Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions lib/puppet/provider/loginctl_user/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,48 @@ def linger=(value)
case value
when :enabled
loginctl('enable-linger', resource[:name])
# Wait for systemd --user instance to be ready after enabling linger
wait_for_user_systemd_ready(resource[:name])
when :disabled
loginctl('disable-linger', resource[:name])
end
end

def wait_for_user_systemd_ready(username, timeout = 10)
# Try to connect to the user's systemd instance using loginctl show-user
# which queries the user's systemd instance state
start_time = Time.now
loop do
begin
# Try to get user runtime directory which indicates systemd --user is running
output = loginctl('show-user', username, '-p', 'RuntimePath')
runtime_path = output.strip.split('=')[1]

# Check if the systemd --user socket exists and is accessible
if runtime_path && !runtime_path.empty?
socket_path = "#{runtime_path}/systemd/private"
if File.exist?(socket_path)
# try to actually communicate with systemd --user
# by checking if the user's systemd is in running state
state_output = loginctl('show-user', username, '-p', 'State')
state = state_output.strip.split('=')[1]
if %w[active lingering].include?(state)
Puppet.debug("systemd --user for #{username} is ready")
break
end
end
end
rescue Puppet::ExecutionFailure => e
# loginctl command failed, systemd --user might not be ready yet
Puppet.debug("Waiting for systemd --user for #{username}: #{e.message}")
end

if Time.now - start_time > timeout
Puppet.warning("Timeout waiting for systemd --user instance for user #{username} to become ready, continuing anyway")
break
end

sleep 1
end
end
end
7 changes: 0 additions & 7 deletions spec/acceptance/user_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@
linger => enabled,
}

# https://github.com/voxpupuli/puppet-systemd/issues/578
exec{'/usr/bin/sleep 10 && touch /tmp/sleep-only-once':
creates => '/tmp/sleep-only-once',
require => Loginctl_user['higgs'],
before => File['/home/higgs/.config'],
}

# Assumes home directory was created as /home/higgs
file{['/home/higgs/.config', '/home/higgs/.config/systemd','/home/higgs/.config/systemd/user']:
ensure => directory,
Expand Down Expand Up @@ -65,7 +58,7 @@
PUPPET
end

it 'works idempotently with no errors' do

Check warning on line 61 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - Debian 11

systemd::user_service create and start a user unit works idempotently with no errors Skipped: we know old OSes do not work

Check warning on line 61 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - Ubuntu 22.04

systemd::user_service create and start a user unit works idempotently with no errors Skipped: we know old OSes do not work

Check warning on line 61 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - Rocky 8

systemd::user_service create and start a user unit works idempotently with no errors Skipped: we know old OSes do not work

Check warning on line 61 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - OracleLinux 8

systemd::user_service create and start a user unit works idempotently with no errors Skipped: we know old OSes do not work

Check warning on line 61 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - AlmaLinux 8

systemd::user_service create and start a user unit works idempotently with no errors Skipped: we know old OSes do not work
skip 'we know old OSes do not work' if
((fact('os.name') == 'Debian') && (fact('os.release.major') == '11')) ||
((fact('os.name') == 'Ubuntu') && (fact('os.release.major') == '22.04')) ||
Expand All @@ -75,7 +68,7 @@
end

describe 'hour.service user unit' do
it 'is running' do

Check warning on line 71 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - Debian 11

systemd::user_service create and start a user unit hour.service user unit is running Skipped: we know old OSes do not work

Check warning on line 71 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - Ubuntu 22.04

systemd::user_service create and start a user unit hour.service user unit is running Skipped: we know old OSes do not work

Check warning on line 71 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - Rocky 8

systemd::user_service create and start a user unit hour.service user unit is running Skipped: we know old OSes do not work

Check warning on line 71 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - OracleLinux 8

systemd::user_service create and start a user unit hour.service user unit is running Skipped: we know old OSes do not work

Check warning on line 71 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - AlmaLinux 8

systemd::user_service create and start a user unit hour.service user unit is running Skipped: we know old OSes do not work
skip 'we know old OSes do not work' if
((fact('os.name') == 'Debian') && (fact('os.release.major') == '11')) ||
((fact('os.name') == 'Ubuntu') && (fact('os.release.major') == '22.04')) ||
Expand All @@ -85,7 +78,7 @@
expect(result.stdout.strip).to eq('active')
end

it 'is enabled' do

Check warning on line 81 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - Debian 11

systemd::user_service create and start a user unit hour.service user unit is enabled Skipped: we know old OSes do not work

Check warning on line 81 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - Ubuntu 22.04

systemd::user_service create and start a user unit hour.service user unit is enabled Skipped: we know old OSes do not work

Check warning on line 81 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - Rocky 8

systemd::user_service create and start a user unit hour.service user unit is enabled Skipped: we know old OSes do not work

Check warning on line 81 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - OracleLinux 8

systemd::user_service create and start a user unit hour.service user unit is enabled Skipped: we know old OSes do not work

Check warning on line 81 in spec/acceptance/user_service_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / OpenVox 8 - AlmaLinux 8

systemd::user_service create and start a user unit hour.service user unit is enabled Skipped: we know old OSes do not work
skip 'we know old OSes do not work' if
((fact('os.name') == 'Debian') && (fact('os.release.major') == '11')) ||
((fact('os.name') == 'Ubuntu') && (fact('os.release.major') == '22.04')) ||
Expand Down
123 changes: 118 additions & 5 deletions spec/unit/puppet/provider/loginctl_user/ruby_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,126 @@
end
end

it 'enables linger' do
resource = Puppet::Type.type(:loginctl_user).new(common_params)
expect(provider_class).to receive(:loginctl).with('enable-linger', 'foo')
resource.provider.linger = :enabled
context 'when enabling linger' do
let(:resource) { Puppet::Type.type(:loginctl_user).new(common_params) }
let(:provider) { resource.provider }

it 'enables linger and waits for systemd --user to be ready' do
expect(provider).to receive(:loginctl).with('enable-linger', 'foo')
runtime_path_called = false
allow(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath') do
runtime_path_called = true
"RuntimePath=/run/user/1000\n"
end

systemd_private_exists_called = false
allow(File).to receive(:exist?).with('/run/user/1000/systemd/private') do
systemd_private_exists_called = true
true
end

state_called = false
allow(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'State') do
state_called = true
"State=active\n"
end
expect(Puppet).to receive(:debug).with('systemd --user for foo is ready')

provider.linger = :enabled

expect(runtime_path_called).to be(true)
expect(systemd_private_exists_called).to be(true)
expect(state_called).to be(true)
end

it 'waits and retries if systemd --user is not immediately ready' do
expect(provider).to receive(:loginctl).with('enable-linger', 'foo')

runtime_path_calls = 0
allow(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath') do
runtime_path_calls += 1
case runtime_path_calls
when 1
"RuntimePath=\n"
else
"RuntimePath=/run/user/1000\n"
end
end

systemd_private_exists_calls = 0
allow(File).to receive(:exist?).with('/run/user/1000/systemd/private') do
systemd_private_exists_calls += 1
case systemd_private_exists_calls
when 1
false
else
true
end
end

state_calls = 0
allow(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'State') do
state_calls += 1
case state_calls
when 1
"State=opening\n"
else
"State=lingering\n"
end
end

expect(Puppet).to receive(:debug).with('systemd --user for foo is ready')

allow(provider).to receive(:sleep)

provider.linger = :enabled

expect(runtime_path_calls).to eq(4)
expect(systemd_private_exists_calls).to eq(3)
expect(state_calls).to eq(2)
end

it 'handles loginctl failures gracefully while waiting' do
expect(provider).to receive(:loginctl).with('enable-linger', 'foo')

runtime_path_calls = 0
allow(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath') do
runtime_path_calls += 1
raise(Puppet::ExecutionFailure, 'Failed to get user properties') if runtime_path_calls == 1

"RuntimePath=/run/user/1000\n"
end

allow(File).to receive(:exist?).with('/run/user/1000/systemd/private').and_return(true)
allow(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'State').and_return("State=active\n")

expect(Puppet).to receive(:debug).with(%r{Waiting for systemd --user for foo})
expect(Puppet).to receive(:debug).with('systemd --user for foo is ready')

allow(provider).to receive(:sleep)

provider.linger = :enabled

expect(runtime_path_calls).to eq(2)
end

it 'logs a warning and continues if timeout is exceeded' do
expect(provider).to receive(:loginctl).with('enable-linger', 'foo')

# Mock Time to simulate timeout
start_time = Time.now
allow(Time).to receive(:now).and_return(start_time, start_time + 11)

allow(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath').
and_return("RuntimePath=\n")

expect(Puppet).to receive(:warning).with(%r{Timeout waiting for systemd --user instance for user foo to become ready, continuing anyway})

provider.linger = :enabled
end
end

it 'disables linger' do
it 'disables linger without waiting' do
resource = Puppet::Type.type(:loginctl_user).new(common_params)
expect(provider_class).to receive(:loginctl).with('disable-linger', 'foo')
resource.provider.linger = :disabled
Expand Down
Loading