Skip to content

Commit 7cdc6ef

Browse files
authored
fix vagrant machine state management. and vagrant status (#14)
1 parent b08acaf commit 7cdc6ef

11 files changed

Lines changed: 164 additions & 61 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
- Distribution names now persist across Vagrant commands (fixes #8)
12+
- `vagrant ssh` and other commands now remember auto-generated distribution names
13+
- `vagrant halt` properly shows "stopped" status instead of waking up the distribution
14+
- State checking no longer wakes up stopped distributions
15+
- `vagrant ssh` commands now silently auto-start stopped distributions without verbose output
16+
17+
### Changed
18+
- Distribution names default to box name (e.g., "Ubuntu") instead of random timestamp
19+
- Added collision detection: appends timestamp if box name already exists as a distribution
20+
- Added `wsl.name` as a convenience alias for `wsl.distribution_name`
21+
- SSH commands now accept WSL2's auto-shutdown behavior and start distributions silently when needed
22+
23+
### Added
24+
- Silent distribution startup for SSH commands to prevent output pollution
25+
- `EnsureRunning` action for transparent VM wake-up on SSH access
26+
1027
## [0.4.0] - 2025-11-24
1128

1229
### Changed

examples/basic/Vagrantfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Vagrant.configure("2") do |config|
77
config.vm.box = "Ubuntu-24.04"
88

99
config.vm.provider "wsl2" do |wsl|
10-
wsl.distribution_name = "vagrant-wsl2-basic"
10+
wsl.name = "vagrant-wsl2-basic"
1111
wsl.version = 2
1212
end
1313
end

lib/vagrant-wsl2-provider/action/create.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,24 @@ def call(env)
1414
config = machine.provider_config
1515
driver = env[:wsl2_driver]
1616

17+
# Generate distribution name if not explicitly set
18+
if !config.distribution_name
19+
# Use box name as the default distribution name
20+
box_name = machine.config.vm.box
21+
22+
# Check if a distribution with this name already exists (collision check)
23+
if wsl_distribution_installed?(box_name)
24+
# Name collision: append timestamp to make it unique
25+
config.distribution_name = "#{box_name}_#{Time.now.to_i}"
26+
env[:ui].warn "Distribution '#{box_name}' already exists, using '#{config.distribution_name}' instead"
27+
else
28+
config.distribution_name = box_name
29+
end
30+
end
31+
1732
env[:ui].info "Creating WSL2 distribution: #{config.distribution_name}"
1833

19-
# Check if distribution already exists
34+
# Check if distribution already exists (and it's not ours)
2035
if driver.state != :not_created
2136
raise Errors::DistributionAlreadyExists,
2237
name: config.distribution_name
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module VagrantPlugins
2+
module WSL2
3+
module Action
4+
# Silently ensure the WSL2 distribution is running
5+
# This is used by SSH commands when the distribution is stopped
6+
# WSL2 automatically stops idle distributions, so this is expected behavior
7+
class EnsureRunning
8+
def initialize(app, env)
9+
@app = app
10+
end
11+
12+
def call(env)
13+
driver = env[:wsl2_driver]
14+
15+
# Only start if stopped, do nothing if already running
16+
# Use silent: true to avoid polluting SSH command output
17+
if driver.state == :stopped
18+
driver.start(silent: true)
19+
end
20+
21+
@app.call(env)
22+
end
23+
end
24+
end
25+
end
26+
end

lib/vagrant-wsl2-provider/communicator.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ def execute(command, opts = nil, &block)
102102

103103
begin
104104
# Build the wsl command with parsed bash flags
105-
wsl_args = ["wsl", "--distribution", config.distribution_name, "-u", "vagrant",
105+
# Use machine.id (persisted) or fallback to config.distribution_name
106+
distribution_name = @machine.id || config.distribution_name
107+
wsl_args = ["wsl", "--distribution", distribution_name, "-u", "vagrant",
106108
"--exec", "bash", bash_flags, wrapped_command]
107109

108110
# Execute the command
@@ -167,7 +169,8 @@ def upload(from, to)
167169

168170
# Get the distribution's filesystem root path
169171
# WSL distributions are accessible at \\wsl$\<distro-name>\
170-
wsl_network_path = "\\\\wsl$\\#{config.distribution_name}"
172+
distribution_name = @machine.id || config.distribution_name
173+
wsl_network_path = "\\\\wsl$\\#{distribution_name}"
171174

172175
# Ensure the distribution is running
173176
unless driver.state == :running
@@ -230,7 +233,8 @@ def download(from, to = nil)
230233
end
231234

232235
# Get the source path in WSL network share
233-
wsl_network_path = "\\\\wsl$\\#{config.distribution_name}"
236+
distribution_name = @machine.id || config.distribution_name
237+
wsl_network_path = "\\\\wsl$\\#{distribution_name}"
234238
from_normalized = from.start_with?('/') ? from[1..-1] : from
235239
source_path = File.join(wsl_network_path, from_normalized)
236240

lib/vagrant-wsl2-provider/config.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ class Config < Vagrant.plugin("2", :config)
6969
# WSL2 distribution name
7070
attr_accessor :distribution_name
7171

72+
# Alias for distribution_name for convenience
73+
alias_method :name, :distribution_name
74+
alias_method :name=, :distribution_name=
75+
7276
# WSL2 version (1 or 2)
7377
attr_accessor :version
7478

@@ -148,7 +152,9 @@ def initialize
148152
end
149153

150154
def finalize!
151-
@distribution_name = "vagrant-#{Time.now.to_i}" if @distribution_name == UNSET_VALUE
155+
# Default distribution_name will be set to nil and generated during Create action
156+
# This prevents generating a new random name on every command
157+
@distribution_name = nil if @distribution_name == UNSET_VALUE
152158
@version = 2 if @version == UNSET_VALUE
153159
@memory = 4096 if @memory == UNSET_VALUE
154160
@cpus = 2 if @cpus == UNSET_VALUE
@@ -160,8 +166,8 @@ def finalize!
160166
def validate(machine)
161167
errors = _detected_errors
162168

163-
# Validate distribution name
164-
if @distribution_name.to_s.strip.empty?
169+
# Validate distribution name (skip if nil, will be auto-generated)
170+
if @distribution_name && @distribution_name.to_s.strip.empty?
165171
errors << "Distribution name cannot be empty"
166172
end
167173

lib/vagrant-wsl2-provider/driver.rb

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,31 @@ def initialize(machine)
99
@config = machine.provider_config
1010
end
1111

12+
# Get the distribution name, preferring machine.id if it exists
13+
def distribution_name
14+
@machine.id || @config.distribution_name
15+
end
16+
1217
# Get the current state of the WSL2 distribution
1318
def state
14-
# Check if distribution exists by trying to get its state
15-
result = Vagrant::Util::Subprocess.execute("wsl", "--distribution", @config.distribution_name, "--exec", "echo")
16-
17-
case result.exit_code
18-
when 0
19-
# Distribution exists and is accessible, check if running
20-
running_result = Vagrant::Util::Subprocess.execute("wsl", "--distribution", @config.distribution_name, "--exec", "true")
21-
return running_result.exit_code == 0 ? :running : :stopped
22-
when 1, 4294967295
23-
# Distribution doesn't exist or WSL error
24-
:not_created
19+
# Return :not_created if we don't have a distribution name yet
20+
return :not_created unless distribution_name
21+
22+
# Use wsl --list --verbose to check state without waking up the distribution
23+
result = execute_safe("wsl", "--list", "--verbose")
24+
return :not_created unless result
25+
26+
distributions = parse_wsl_list_output(result.stdout)
27+
distro = distributions.find { |d| d[:name] == distribution_name }
28+
29+
return :not_created unless distro
30+
31+
case distro[:state]
32+
when "Running"
33+
:running
34+
when "Stopped"
35+
:stopped
2536
else
26-
# Unknown error state
2737
:unknown
2838
end
2939
rescue
@@ -37,25 +47,26 @@ def create(box_path)
3747
FileUtils.mkdir_p(dist_dir) unless File.exist?(dist_dir)
3848

3949
# Import the distribution from a tar.gz file
40-
execute("wsl", "--import", @config.distribution_name,
50+
execute("wsl", "--import", distribution_name,
4151
dist_dir, box_path, "--version", @config.version.to_s)
4252
end
4353

4454
# Start the WSL2 distribution
45-
def start
46-
@machine.ui.info "Starting WSL2 distribution: #{@config.distribution_name}"
47-
execute("wsl", "--distribution", @config.distribution_name, "--exec", "true")
55+
# @param silent [Boolean] If true, suppress UI output
56+
def start(silent: false)
57+
@machine.ui.info "Starting WSL2 distribution: #{distribution_name}" unless silent
58+
execute("wsl", "--distribution", distribution_name, "--exec", "true")
4859
end
4960

5061
# Stop the WSL2 distribution
5162
def halt
52-
@machine.ui.info "Stopping WSL2 distribution: #{@config.distribution_name}"
53-
execute("wsl", "--terminate", @config.distribution_name)
63+
@machine.ui.info "Stopping WSL2 distribution: #{distribution_name}"
64+
execute("wsl", "--terminate", distribution_name)
5465
end
5566

5667
# Destroy the WSL2 distribution
5768
def destroy
58-
execute("wsl", "--unregister", @config.distribution_name)
69+
execute("wsl", "--unregister", distribution_name)
5970

6071
# Wait a moment for WSL to release file handles
6172
sleep 1
@@ -82,7 +93,7 @@ def destroy
8293

8394
# Execute a command in the WSL2 distribution
8495
def execute_in_wsl(*args)
85-
execute("wsl", "--distribution", @config.distribution_name, *args)
96+
execute("wsl", "--distribution", distribution_name, *args)
8697
end
8798

8899
# Public wrapper for execute method (for use by actions)
@@ -139,7 +150,7 @@ def save_snapshot(snapshot_name)
139150

140151
# Export the current distribution to a tar file
141152
@machine.ui.info "Saving snapshot: #{snapshot_name}"
142-
execute("wsl", "--export", @config.distribution_name, snapshot_file)
153+
execute("wsl", "--export", distribution_name, snapshot_file)
143154

144155
@machine.ui.success "Snapshot saved: #{snapshot_name}"
145156
end
@@ -156,13 +167,13 @@ def restore_snapshot(snapshot_name)
156167

157168
# First, unregister the current distribution
158169
halt if state == :running
159-
execute("wsl", "--unregister", @config.distribution_name)
170+
execute("wsl", "--unregister", distribution_name)
160171

161172
# Import the snapshot as the distribution
162173
dist_dir = distribution_path
163174
FileUtils.mkdir_p(dist_dir) unless File.exist?(dist_dir)
164175

165-
execute("wsl", "--import", @config.distribution_name,
176+
execute("wsl", "--import", distribution_name,
166177
dist_dir, snapshot_file, "--version", @config.version.to_s)
167178

168179
@machine.ui.success "Snapshot restored: #{snapshot_name}"
@@ -267,7 +278,7 @@ def data_disk_already_mounted?(expected_disk_count)
267278
# Count block devices that could be data disks (sde and later)
268279
# Use wsl -d to run command inside the distribution
269280
result = Vagrant::Util::Subprocess.execute(
270-
"wsl", "-d", @config.distribution_name, "--", "lsblk", "-nd", "-o", "NAME"
281+
"wsl", "-d", distribution_name, "--", "lsblk", "-nd", "-o", "NAME"
271282
)
272283
return false if result.exit_code != 0
273284

@@ -424,6 +435,39 @@ def execute_safe(*args)
424435
def distribution_path
425436
@machine.data_dir.join("wsl2_distribution").to_s
426437
end
438+
439+
# Parse WSL list output to extract distribution information
440+
def parse_wsl_list_output(output)
441+
distributions = []
442+
443+
# Handle UTF-16LE encoding from WSL on Windows
444+
output = output.force_encoding('UTF-16LE').encode('UTF-8', invalid: :replace, undef: :replace)
445+
446+
lines = output.lines.map(&:strip).reject(&:empty?)
447+
448+
# Find the header line (NAME STATE VERSION)
449+
header_index = lines.find_index { |line| line.match?(/NAME.*STATE.*VERSION/i) }
450+
return distributions unless header_index
451+
452+
# Parse distribution lines after the header
453+
lines[(header_index + 1)..-1].each do |line|
454+
# Remove default marker (*) and null bytes
455+
line = line.gsub(/\*/, '').gsub(/\0/, '').strip
456+
next if line.empty?
457+
458+
# Split by whitespace and extract fields
459+
parts = line.split(/\s+/)
460+
next if parts.length < 3
461+
462+
distributions << {
463+
name: parts[0],
464+
state: parts[1],
465+
version: parts[2]
466+
}
467+
end
468+
469+
distributions
470+
end
427471
end
428472
end
429473
end

lib/vagrant-wsl2-provider/provider.rb

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module Action
99
autoload :Destroy, File.expand_path("../action/destroy", __FILE__)
1010
autoload :Halt, File.expand_path("../action/halt", __FILE__)
1111
autoload :Start, File.expand_path("../action/start", __FILE__)
12+
autoload :EnsureRunning, File.expand_path("../action/ensure_running", __FILE__)
1213
autoload :PrepareEnvironment, File.expand_path("../action/prepare_environment", __FILE__)
1314
autoload :WSLShell, File.expand_path("../action/wsl_shell", __FILE__)
1415
autoload :ShareFolders, File.expand_path("../action/share_folders", __FILE__)
@@ -165,33 +166,19 @@ def action_destroy
165166
def action_ssh
166167
Vagrant::Action::Builder.new.tap do |b|
167168
b.use Vagrant::Action::Builtin::ConfigValidate
168-
b.use Vagrant::Action::Builtin::Call, Vagrant::Action::Builtin::IsState, :running do |env, b2|
169-
if env[:result]
170-
b2.use Action::PrepareEnvironment
171-
b2.use Action::WSLShell
172-
else
173-
env[:ui].info("Machine is not running, starting it up...")
174-
b2.use action_up
175-
b2.use Action::WSLShell
176-
end
177-
end
169+
b.use Action::PrepareEnvironment
170+
b.use Action::EnsureRunning
171+
b.use Action::WSLShell
178172
end
179173
end
180174

181175
# Define action middleware for 'vagrant ssh -c command'
182176
def action_ssh_run
183177
Vagrant::Action::Builder.new.tap do |b|
184178
b.use Vagrant::Action::Builtin::ConfigValidate
185-
b.use Vagrant::Action::Builtin::Call, Vagrant::Action::Builtin::IsState, :running do |env, b2|
186-
if env[:result]
187-
b2.use Action::PrepareEnvironment
188-
b2.use Action::SSHRun
189-
else
190-
env[:ui].info("Machine is not running, starting it up...")
191-
b2.use action_up
192-
b2.use Action::SSHRun
193-
end
194-
end
179+
b.use Action::PrepareEnvironment
180+
b.use Action::EnsureRunning
181+
b.use Action::SSHRun
195182
end
196183
end
197184

test/integration/Basic.Tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ Describe "Vagrant WSL2 Provider - Basic Operations" {
4848
}
4949

5050
It "Should execute commands via 'vagrant ssh -c'" {
51-
$sshOutput = vagrant ssh -c "echo 'SSH test successful'" 2>&1
51+
$sshOutput = (vagrant ssh -c "echo 'SSH test successful'" 2>&1) -join "`n"
5252
$LASTEXITCODE | Should -Be 0
5353
$sshOutput | Should -Match "SSH test successful"
5454
}
5555

5656
It "Should handle SSH commands with pipes" {
57-
$output = vagrant ssh -c "echo 'test' | grep 'test'" 2>&1
57+
$output = (vagrant ssh -c "echo 'test' | grep 'test'" 2>&1) -join "`n"
5858
$LASTEXITCODE | Should -Be 0
5959
$output | Should -Match "test"
6060
}

test/integration/Init.Tests.ps1

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
BeforeAll {
66
$script:ExampleDir = Join-Path $PSScriptRoot "..\..\examples\init"
7-
$script:DistributionName = "vagrant-wsl2-init"
7+
# Default distribution name should be the box name
8+
$script:DistributionName = "Ubuntu"
89
$script:VagrantfilePath = Join-Path $script:ExampleDir "Vagrantfile"
910

1011
# Ensure we're in the correct directory
@@ -52,8 +53,9 @@ Describe "Vagrant WSL2 Provider - Init Workflow" {
5253
$LASTEXITCODE | Should -Be 0
5354
}
5455

55-
It "Should create WSL distribution" {
56+
It "Should create WSL distribution using box name" {
5657
$wslList = (wsl -l -v | Out-String) -replace '\0', ''
58+
# Distribution name should be the box name (Ubuntu)
5759
$wslList | Should -Match $script:DistributionName
5860
}
5961

0 commit comments

Comments
 (0)