Skip to content

Commit 8b118eb

Browse files
committed
wait for user when using linger
1 parent 14155da commit 8b118eb

File tree

3 files changed

+124
-12
lines changed

3 files changed

+124
-12
lines changed

lib/puppet/provider/loginctl_user/ruby.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,48 @@ def linger=(value)
3333
case value
3434
when :enabled
3535
loginctl('enable-linger', resource[:name])
36+
# Wait for systemd --user instance to be ready after enabling linger
37+
wait_for_user_systemd_ready(resource[:name])
3638
when :disabled
3739
loginctl('disable-linger', resource[:name])
3840
end
3941
end
42+
43+
def wait_for_user_systemd_ready(username, timeout = 10)
44+
# Try to connect to the user's systemd instance using loginctl show-user
45+
# which queries the user's systemd instance state
46+
start_time = Time.now
47+
loop do
48+
begin
49+
# Try to get user runtime directory which indicates systemd --user is running
50+
output = loginctl('show-user', username, '-p', 'RuntimePath')
51+
runtime_path = output.strip.split('=')[1]
52+
53+
# Check if the systemd --user socket exists and is accessible
54+
if runtime_path && !runtime_path.empty?
55+
socket_path = "#{runtime_path}/systemd/private"
56+
if File.exist?(socket_path)
57+
# try to actually communicate with systemd --user
58+
# by checking if the user's systemd is in running state
59+
state_output = loginctl('show-user', username, '-p', 'State')
60+
state = state_output.strip.split('=')[1]
61+
if %w[active lingering].include?(state)
62+
Puppet.debug("systemd --user for #{username} is ready")
63+
break
64+
end
65+
end
66+
end
67+
rescue Puppet::ExecutionFailure => e
68+
# loginctl command failed, systemd --user might not be ready yet
69+
Puppet.debug("Waiting for systemd --user for #{username}: #{e.message}")
70+
end
71+
72+
if Time.now - start_time > timeout
73+
Puppet.warning("Timeout waiting for systemd --user instance for user #{username} to become ready, continuing anyway")
74+
break
75+
end
76+
77+
sleep 1
78+
end
79+
end
4080
end

spec/acceptance/user_service_spec.rb

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,6 @@
2222
linger => enabled,
2323
}
2424
25-
# https://github.com/voxpupuli/puppet-systemd/issues/578
26-
exec{'/usr/bin/sleep 10 && touch /tmp/sleep-only-once':
27-
creates => '/tmp/sleep-only-once',
28-
require => Loginctl_user['higgs'],
29-
before => File['/home/higgs/.config'],
30-
}
31-
3225
# Assumes home directory was created as /home/higgs
3326
file{['/home/higgs/.config', '/home/higgs/.config/systemd','/home/higgs/.config/systemd/user']:
3427
ensure => directory,

spec/unit/puppet/provider/loginctl_user/ruby_spec.rb

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,92 @@
2727
end
2828
end
2929

30-
it 'enables linger' do
31-
resource = Puppet::Type.type(:loginctl_user).new(common_params)
32-
expect(provider_class).to receive(:loginctl).with('enable-linger', 'foo')
33-
resource.provider.linger = :enabled
30+
context 'when enabling linger' do
31+
let(:resource) { Puppet::Type.type(:loginctl_user).new(common_params) }
32+
let(:provider) { resource.provider }
33+
34+
it 'enables linger and waits for systemd --user to be ready' do
35+
expect(provider).to receive(:loginctl).with('enable-linger', 'foo')
36+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath').
37+
and_return("RuntimePath=/run/user/1000\n")
38+
expect(File).to receive(:exist?).with('/run/user/1000/systemd/private').and_return(true)
39+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'State').
40+
and_return("State=active\n")
41+
expect(Puppet).to receive(:debug).with('systemd --user for foo is ready')
42+
43+
provider.linger = :enabled
44+
end
45+
46+
it 'waits and retries if systemd --user is not immediately ready' do
47+
expect(provider).to receive(:loginctl).with('enable-linger', 'foo')
48+
49+
# First attempt: RuntimePath not ready
50+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath').
51+
and_return("RuntimePath=\n")
52+
53+
# Second attempt: Socket doesn't exist yet
54+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath').
55+
and_return("RuntimePath=/run/user/1000\n")
56+
expect(File).to receive(:exist?).with('/run/user/1000/systemd/private').and_return(false)
57+
58+
# Third attempt: State not active yet
59+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath').
60+
and_return("RuntimePath=/run/user/1000\n")
61+
expect(File).to receive(:exist?).with('/run/user/1000/systemd/private').and_return(true)
62+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'State').
63+
and_return("State=opening\n")
64+
65+
# Fourth attempt: Success with lingering state
66+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath').
67+
and_return("RuntimePath=/run/user/1000\n")
68+
expect(File).to receive(:exist?).with('/run/user/1000/systemd/private').and_return(true)
69+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'State').
70+
and_return("State=lingering\n")
71+
expect(Puppet).to receive(:debug).with('systemd --user for foo is ready')
72+
73+
allow(provider).to receive(:sleep)
74+
75+
provider.linger = :enabled
76+
end
77+
78+
it 'handles loginctl failures gracefully while waiting' do
79+
expect(provider).to receive(:loginctl).with('enable-linger', 'foo')
80+
81+
# First attempt: loginctl fails
82+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath').
83+
and_raise(Puppet::ExecutionFailure, 'Failed to get user properties')
84+
expect(Puppet).to receive(:debug).with(%r{Waiting for systemd --user for foo})
85+
86+
# Second attempt: Success
87+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath').
88+
and_return("RuntimePath=/run/user/1000\n")
89+
expect(File).to receive(:exist?).with('/run/user/1000/systemd/private').and_return(true)
90+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'State').
91+
and_return("State=active\n")
92+
expect(Puppet).to receive(:debug).with('systemd --user for foo is ready')
93+
94+
allow(provider).to receive(:sleep)
95+
96+
provider.linger = :enabled
97+
end
98+
99+
it 'logs a warning and continues if timeout is exceeded' do
100+
expect(provider).to receive(:loginctl).with('enable-linger', 'foo')
101+
102+
# Mock Time to simulate timeout
103+
start_time = Time.now
104+
allow(Time).to receive(:now).and_return(start_time, start_time + 11)
105+
106+
expect(provider).to receive(:loginctl).with('show-user', 'foo', '-p', 'RuntimePath').
107+
and_return("RuntimePath=\n")
108+
109+
expect(Puppet).to receive(:warning).with(%r{Timeout waiting for systemd --user instance for user foo to become ready, continuing anyway})
110+
111+
provider.linger = :enabled
112+
end
34113
end
35114

36-
it 'disables linger' do
115+
it 'disables linger without waiting' do
37116
resource = Puppet::Type.type(:loginctl_user).new(common_params)
38117
expect(provider_class).to receive(:loginctl).with('disable-linger', 'foo')
39118
resource.provider.linger = :disabled

0 commit comments

Comments
 (0)