foreman-export-systemd_user 0.1.1 → 0.2.0
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 +4 -4
- data/.github/workflows/ci.yml +27 -0
- data/README.md +21 -1
- data/Rakefile +5 -0
- data/bin/foreman +14 -0
- data/bin/setup +10 -0
- data/data/export/systemd_user/process_master.target.erb +2 -0
- data/foreman-export-systemd_user.gemspec +6 -4
- data/lib/foreman/export/systemd_user.rb +47 -14
- data/spec/Dockerfile.systemd +38 -0
- data/spec/foreman/export/systemd_user_spec.rb +160 -0
- data/spec/spec_helper.rb +102 -0
- metadata +33 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1da014b907b4e3d9b607f3a8d2309bf8c4a294d96ae4b87c427a2adf3dfc08b
|
|
4
|
+
data.tar.gz: 6ae264bb76352d92fecd9810c9c2353ea623ea49a9f99ec1f8bed7b1413b2088
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a69c9692b060a6248edb0a46b759a7218bfbc614f1a458420f064be5a4b33e24c0dd232dae69c67a6a966533f7a81d1f1828e73ac0944b4c10081716a24068d
|
|
7
|
+
data.tar.gz: 6e4f1042467b14f69a453690672741e60ef7c94d670db12363203a9f6c7fb0597b53bab8cd17b8d1e687aaf715db7f97345a7b3783f3af89d1f7cd858772cf0f
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-22.04
|
|
8
|
+
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
|
|
12
|
+
- name: Set up Ruby
|
|
13
|
+
uses: ruby/setup-ruby@v1
|
|
14
|
+
with:
|
|
15
|
+
ruby-version: 3.3
|
|
16
|
+
bundler-cache: true
|
|
17
|
+
|
|
18
|
+
- name: Install podman
|
|
19
|
+
run: |
|
|
20
|
+
sudo apt-get update
|
|
21
|
+
sudo apt-get install -y podman
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: bundle install
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: bundle exec rspec
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Foreman export scripts for user-level systemd
|
|
1
|
+
# Foreman export scripts for user-level systemd
|
|
2
2
|
|
|
3
3
|
```ruby
|
|
4
4
|
# Gemfile
|
|
@@ -13,9 +13,29 @@ bundle exec foreman export systemd-user --app <app-name>
|
|
|
13
13
|
|
|
14
14
|
Note that this may break from foreman's protocol a bit, because it starts the processes after export. It does this by running the following:
|
|
15
15
|
```
|
|
16
|
+
systemctl --user daemon-reload
|
|
16
17
|
loginctl enable-linger
|
|
17
18
|
systemctl --user enable <app-name>.target
|
|
18
19
|
systemctl --user restart <app-name>.target
|
|
19
20
|
```
|
|
20
21
|
After forgetting to run these steps enough times, I just decided to bake it into the export.
|
|
21
22
|
|
|
23
|
+
## Including extra systemd files
|
|
24
|
+
|
|
25
|
+
Use `--include-dir` to copy additional systemd files (drop-in overrides, extra units, timers) after generating the main units:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
bundle exec foreman export systemd-user --app <app-name> --include-dir Procfile.systemd
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Example directory structure:
|
|
32
|
+
```
|
|
33
|
+
Procfile.systemd/
|
|
34
|
+
<app-name>-web@.service.d/
|
|
35
|
+
override.conf # Drop-in override for the web service
|
|
36
|
+
<app-name>-restart.service # Extra standalone unit
|
|
37
|
+
<app-name>-restart.timer # Timer (will be enabled and started automatically)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Any `.timer` files in the root of the include directory will be enabled with `--now`.
|
|
41
|
+
|
data/Rakefile
CHANGED
data/bin/foreman
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "rubygems"
|
|
4
|
+
require "foreman/cli"
|
|
5
|
+
require "foreman-export-systemd_user"
|
|
6
|
+
|
|
7
|
+
# Add --include-dir option to foreman export command
|
|
8
|
+
Foreman::CLI.class_eval do
|
|
9
|
+
option = Thor::Option.new(:include_dir, type: :string, desc: "Directory of additional systemd files to copy")
|
|
10
|
+
export_command = commands["export"]
|
|
11
|
+
export_command.options[:include_dir] = option
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Foreman::CLI.start
|
data/bin/setup
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
echo "==> Installing gem dependencies..."
|
|
5
|
+
bundle install
|
|
6
|
+
|
|
7
|
+
echo "==> Building test container image..."
|
|
8
|
+
podman build -t foreman-export-systemd-user-test -f spec/Dockerfile.systemd spec/
|
|
9
|
+
|
|
10
|
+
echo "==> Setup complete! Run 'bundle exec rake spec' to run tests."
|
|
@@ -2,17 +2,19 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = "foreman-export-systemd_user"
|
|
5
|
-
s.version = "0.
|
|
5
|
+
s.version = "0.2.0"
|
|
6
6
|
s.authors = ["Micah Geisel"]
|
|
7
7
|
s.email = ["micah@botandrose.com"]
|
|
8
8
|
s.homepage = "http://github.com/botandrose/foreman-export-systemd_user"
|
|
9
|
-
s.summary = "
|
|
10
|
-
s.description = "
|
|
9
|
+
s.summary = "Foreman export scripts for user-level systemd"
|
|
10
|
+
s.description = "Foreman export scripts for user-level systemd"
|
|
11
11
|
|
|
12
12
|
s.files = `git ls-files`.split("\n")
|
|
13
13
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
14
14
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
15
15
|
s.require_paths = ["lib"]
|
|
16
16
|
|
|
17
|
-
s.add_runtime_dependency "foreman", "0.
|
|
17
|
+
s.add_runtime_dependency "foreman", ">= 0.90.0"
|
|
18
|
+
|
|
19
|
+
s.add_development_dependency "rspec"
|
|
18
20
|
end
|
|
@@ -2,14 +2,12 @@ require "erb"
|
|
|
2
2
|
require "foreman/export"
|
|
3
3
|
|
|
4
4
|
class Foreman::Export::SystemdUser < Foreman::Export::Base
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@options = { template: template }.merge(@options).freeze
|
|
12
|
-
end
|
|
5
|
+
TEMPLATE_DIR = File.expand_path("../../../../data/export/systemd_user", __FILE__)
|
|
6
|
+
|
|
7
|
+
def initialize(location, engine, options = {})
|
|
8
|
+
options = options.dup
|
|
9
|
+
options[:template] ||= TEMPLATE_DIR
|
|
10
|
+
super(location, engine, options)
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
def app
|
|
@@ -22,7 +20,15 @@ class Foreman::Export::SystemdUser < Foreman::Export::Base
|
|
|
22
20
|
|
|
23
21
|
def export
|
|
24
22
|
super
|
|
23
|
+
clean_old_units
|
|
24
|
+
write_units
|
|
25
|
+
install_include_dir
|
|
26
|
+
configure_systemd
|
|
27
|
+
end
|
|
25
28
|
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def clean_old_units
|
|
26
32
|
Dir["#{location}/#{app}*.target"]
|
|
27
33
|
.concat(Dir["#{location}/#{app}*.service"])
|
|
28
34
|
.concat(Dir["#{location}/#{app}*.target.wants/#{app}*.service"])
|
|
@@ -33,7 +39,9 @@ class Foreman::Export::SystemdUser < Foreman::Export::Base
|
|
|
33
39
|
Dir["#{location}/#{app}*.target.wants"].each do |file|
|
|
34
40
|
clean_dir file
|
|
35
41
|
end
|
|
42
|
+
end
|
|
36
43
|
|
|
44
|
+
def write_units
|
|
37
45
|
process_master_names = []
|
|
38
46
|
|
|
39
47
|
engine.each_process do |name, process|
|
|
@@ -48,22 +56,47 @@ class Foreman::Export::SystemdUser < Foreman::Export::Base
|
|
|
48
56
|
create_symlink("#{app}-#{name}.target.wants/#{process_name}", "../#{service_fn}") rescue Errno::EEXIST # This is needed because rr-mocks do not call the origial cleanup
|
|
49
57
|
end
|
|
50
58
|
|
|
51
|
-
write_template "
|
|
59
|
+
write_template "systemd_user/process_master.target.erb", "#{app}-#{name}.target", binding
|
|
52
60
|
process_master_names << "#{app}-#{name}.target"
|
|
53
61
|
end
|
|
54
62
|
|
|
55
63
|
write_template "systemd_user/master.target.erb", "#{app}.target", binding
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def run_command command
|
|
67
|
+
puts command
|
|
68
|
+
raise unless system(command)
|
|
69
|
+
end
|
|
56
70
|
|
|
71
|
+
def include_dir
|
|
72
|
+
dir = options[:include_dir]
|
|
73
|
+
return unless dir
|
|
74
|
+
|
|
75
|
+
raise "include_dir '#{dir}' is not a directory" unless File.directory?(dir)
|
|
76
|
+
dir
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def install_include_dir
|
|
80
|
+
if include_dir
|
|
81
|
+
run_command "cp -r #{include_dir}/. #{location}/"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def configure_systemd
|
|
86
|
+
run_command "systemctl --user daemon-reload"
|
|
57
87
|
run_command "test -f /var/lib/systemd/linger/$USER || loginctl enable-linger"
|
|
58
88
|
run_command "systemctl --user enable #{app}.target"
|
|
59
89
|
run_command "systemctl --user restart #{app}.target"
|
|
90
|
+
enable_timers
|
|
60
91
|
end
|
|
61
92
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
93
|
+
def enable_timers
|
|
94
|
+
if include_dir
|
|
95
|
+
Dir.glob("#{include_dir}/*.timer").each do |timer|
|
|
96
|
+
timer_name = File.basename(timer)
|
|
97
|
+
run_command "systemctl --user enable --now #{timer_name}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
67
100
|
end
|
|
68
101
|
end
|
|
69
102
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
FROM ubuntu:22.04
|
|
2
|
+
|
|
3
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
4
|
+
|
|
5
|
+
RUN apt-get update && apt-get install -y \
|
|
6
|
+
systemd \
|
|
7
|
+
systemd-sysv \
|
|
8
|
+
dbus \
|
|
9
|
+
dbus-user-session \
|
|
10
|
+
ruby \
|
|
11
|
+
ruby-bundler \
|
|
12
|
+
git \
|
|
13
|
+
&& apt-get clean \
|
|
14
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
15
|
+
|
|
16
|
+
# Create test user
|
|
17
|
+
RUN useradd -m -s /bin/bash testuser \
|
|
18
|
+
&& mkdir -p /home/testuser/.config/systemd/user \
|
|
19
|
+
&& chown -R testuser:testuser /home/testuser
|
|
20
|
+
|
|
21
|
+
# Enable linger for testuser so user services start at boot
|
|
22
|
+
RUN mkdir -p /var/lib/systemd/linger \
|
|
23
|
+
&& touch /var/lib/systemd/linger/testuser
|
|
24
|
+
|
|
25
|
+
# Remove unnecessary systemd services that won't work in container
|
|
26
|
+
RUN rm -f /lib/systemd/system/multi-user.target.wants/* \
|
|
27
|
+
/etc/systemd/system/*.wants/* \
|
|
28
|
+
/lib/systemd/system/local-fs.target.wants/* \
|
|
29
|
+
/lib/systemd/system/sockets.target.wants/*udev* \
|
|
30
|
+
/lib/systemd/system/sockets.target.wants/*initctl* \
|
|
31
|
+
/lib/systemd/system/sysinit.target.wants/systemd-tmpfiles-setup* \
|
|
32
|
+
/lib/systemd/system/systemd-update-utmp*
|
|
33
|
+
|
|
34
|
+
VOLUME ["/sys/fs/cgroup"]
|
|
35
|
+
|
|
36
|
+
STOPSIGNAL SIGRTMIN+3
|
|
37
|
+
|
|
38
|
+
CMD ["/sbin/init"]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe "foreman export systemd-user" do
|
|
4
|
+
let(:container) { SystemdContainer }
|
|
5
|
+
let(:app_name) { "testapp" }
|
|
6
|
+
let(:systemd_dir) { container.user_systemd_dir }
|
|
7
|
+
|
|
8
|
+
before(:all) do
|
|
9
|
+
# Copy gem files to container and install
|
|
10
|
+
@gem_path = SystemdContainer.copy_gem_to_container
|
|
11
|
+
SystemdContainer.exec("bash", "-c", "cd #{@gem_path} && bundle install --quiet 2>&1")
|
|
12
|
+
|
|
13
|
+
# Set up a minimal Procfile in the test user's app directory
|
|
14
|
+
SystemdContainer.exec_as_user("mkdir -p /home/testuser/app")
|
|
15
|
+
SystemdContainer.exec_as_user("echo 'web: ruby -run -e httpd . -p $PORT' > /home/testuser/app/Procfile")
|
|
16
|
+
|
|
17
|
+
# Create a Gemfile in the app dir that references our gem
|
|
18
|
+
SystemdContainer.exec_as_user("cat > /home/testuser/app/Gemfile << EOF
|
|
19
|
+
source 'https://rubygems.org'
|
|
20
|
+
gem 'foreman-export-systemd_user', path: '#{@gem_path}'
|
|
21
|
+
EOF")
|
|
22
|
+
SystemdContainer.exec_as_user("cd /home/testuser/app && bundle config set --local path 'vendor/bundle' && bundle install --quiet 2>&1")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "basic export" do
|
|
26
|
+
before(:all) do
|
|
27
|
+
stdout, stderr, status = SystemdContainer.exec_as_user(
|
|
28
|
+
"cd /home/testuser/app && bundle exec foreman export systemd-user --app testapp 2>&1"
|
|
29
|
+
)
|
|
30
|
+
@export_output = stdout
|
|
31
|
+
@export_status = status
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "succeeds" do
|
|
35
|
+
expect(@export_status).to be_success
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "creates the master target file" do
|
|
39
|
+
stdout, _, _ = container.exec_as_user("cat #{systemd_dir}/testapp.target")
|
|
40
|
+
expect(stdout).to include("[Unit]")
|
|
41
|
+
expect(stdout).to include("testapp-web.target")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "creates the process target file" do
|
|
45
|
+
stdout, _, _ = container.exec_as_user("cat #{systemd_dir}/testapp-web.target")
|
|
46
|
+
expect(stdout).to include("[Unit]")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "creates the process service template" do
|
|
50
|
+
stdout, _, _ = container.exec_as_user("cat #{systemd_dir}/testapp-web@.service")
|
|
51
|
+
expect(stdout).to include("[Service]")
|
|
52
|
+
expect(stdout).to include("ExecStart=")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "creates the target.wants directory with symlinks" do
|
|
56
|
+
stdout, _, _ = container.exec_as_user("ls -la #{systemd_dir}/testapp-web.target.wants/")
|
|
57
|
+
expect(stdout).to include("testapp-web@")
|
|
58
|
+
expect(stdout).to include("-> ../testapp-web@.service")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "runs daemon-reload" do
|
|
62
|
+
expect(@export_output).to include("systemctl --user daemon-reload")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "enables linger" do
|
|
66
|
+
expect(@export_output).to include("loginctl enable-linger")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "enables the target" do
|
|
70
|
+
expect(@export_output).to include("systemctl --user enable testapp.target")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "the target can be started" do
|
|
74
|
+
stdout, _, _ = container.exec_as_user("systemctl --user start testapp.target && systemctl --user is-active testapp.target")
|
|
75
|
+
expect(stdout.strip).to eq("active")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe "with --include-dir" do
|
|
80
|
+
before(:all) do
|
|
81
|
+
# Create include-dir with drop-in and timer
|
|
82
|
+
SystemdContainer.exec_as_user("mkdir -p /home/testuser/app/Procfile.systemd/testapp-web@.service.d")
|
|
83
|
+
SystemdContainer.exec_as_user("echo '[Service]\nEnvironment=EXTRA=value' > /home/testuser/app/Procfile.systemd/testapp-web@.service.d/override.conf")
|
|
84
|
+
|
|
85
|
+
# Create a simple timer
|
|
86
|
+
SystemdContainer.exec_as_user("cat > /home/testuser/app/Procfile.systemd/testapp-cleanup.timer << 'EOF'
|
|
87
|
+
[Unit]
|
|
88
|
+
Description=Cleanup timer
|
|
89
|
+
|
|
90
|
+
[Timer]
|
|
91
|
+
OnCalendar=daily
|
|
92
|
+
|
|
93
|
+
[Install]
|
|
94
|
+
WantedBy=timers.target
|
|
95
|
+
EOF")
|
|
96
|
+
|
|
97
|
+
SystemdContainer.exec_as_user("cat > /home/testuser/app/Procfile.systemd/testapp-cleanup.service << 'EOF'
|
|
98
|
+
[Unit]
|
|
99
|
+
Description=Cleanup service
|
|
100
|
+
|
|
101
|
+
[Service]
|
|
102
|
+
Type=oneshot
|
|
103
|
+
ExecStart=/bin/true
|
|
104
|
+
EOF")
|
|
105
|
+
|
|
106
|
+
# Stop any existing target first
|
|
107
|
+
SystemdContainer.exec_as_user("systemctl --user stop testapp.target 2>/dev/null || true")
|
|
108
|
+
|
|
109
|
+
# Run export with --include-dir
|
|
110
|
+
stdout, stderr, status = SystemdContainer.exec_as_user(
|
|
111
|
+
"cd /home/testuser/app && bundle exec foreman export systemd-user --app testapp --include-dir Procfile.systemd 2>&1"
|
|
112
|
+
)
|
|
113
|
+
@export_output = stdout
|
|
114
|
+
@export_status = status
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "succeeds" do
|
|
118
|
+
expect(@export_status).to be_success
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "copies the drop-in directory" do
|
|
122
|
+
stdout, _, _ = container.exec_as_user("cat #{systemd_dir}/testapp-web@.service.d/override.conf")
|
|
123
|
+
expect(stdout).to include("EXTRA=value")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "copies the timer file" do
|
|
127
|
+
stdout, _, _ = container.exec_as_user("cat #{systemd_dir}/testapp-cleanup.timer")
|
|
128
|
+
expect(stdout).to include("OnCalendar=daily")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "copies the service file" do
|
|
132
|
+
stdout, _, _ = container.exec_as_user("cat #{systemd_dir}/testapp-cleanup.service")
|
|
133
|
+
expect(stdout).to include("ExecStart=/bin/true")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "enables the timer with --now" do
|
|
137
|
+
expect(@export_output).to include("systemctl --user enable --now testapp-cleanup.timer")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "the timer is active" do
|
|
141
|
+
stdout, _, _ = container.exec_as_user("systemctl --user is-active testapp-cleanup.timer")
|
|
142
|
+
expect(stdout.strip).to eq("active")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "the drop-in is applied" do
|
|
146
|
+
stdout, _, _ = container.exec_as_user("systemctl --user show testapp-web@5000.service -p Environment")
|
|
147
|
+
expect(stdout).to include("EXTRA=value")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "error handling" do
|
|
152
|
+
it "raises error when include-dir is not a directory" do
|
|
153
|
+
stdout, _, status = container.exec_as_user(
|
|
154
|
+
"cd /home/testuser/app && bundle exec foreman export systemd-user --app testapp --include-dir /nonexistent 2>&1"
|
|
155
|
+
)
|
|
156
|
+
expect(status).not_to be_success
|
|
157
|
+
expect(stdout).to include("not a directory")
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
|
|
5
|
+
module SystemdContainer
|
|
6
|
+
DOCKERFILE_PATH = File.expand_path("Dockerfile.systemd", __dir__)
|
|
7
|
+
IMAGE_NAME = "foreman-export-systemd-user-test"
|
|
8
|
+
CONTAINER_NAME = "foreman-export-systemd-user-test-container"
|
|
9
|
+
TEST_USER = "testuser"
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def start
|
|
13
|
+
build_image
|
|
14
|
+
stop
|
|
15
|
+
|
|
16
|
+
cmd = [
|
|
17
|
+
"podman", "run", "-d",
|
|
18
|
+
"--name", CONTAINER_NAME,
|
|
19
|
+
"--privileged",
|
|
20
|
+
"-v", "/sys/fs/cgroup:/sys/fs/cgroup:rw",
|
|
21
|
+
IMAGE_NAME
|
|
22
|
+
]
|
|
23
|
+
run_command(cmd)
|
|
24
|
+
wait_for_systemd
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def stop
|
|
28
|
+
system("podman", "stop", CONTAINER_NAME, out: File::NULL, err: File::NULL)
|
|
29
|
+
system("podman", "rm", CONTAINER_NAME, out: File::NULL, err: File::NULL)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def exec(*command)
|
|
33
|
+
cmd = ["podman", "exec", CONTAINER_NAME] + command.flatten
|
|
34
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
|
35
|
+
[stdout, stderr, status]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def exec_as_user(command)
|
|
39
|
+
exec("su", "-", TEST_USER, "-c", command)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def user_systemd_dir
|
|
43
|
+
"/home/#{TEST_USER}/.config/systemd/user"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def copy_to_container(src, dest)
|
|
47
|
+
run_command(["podman", "cp", src, "#{CONTAINER_NAME}:#{dest}"])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def copy_gem_to_container(dest = "/tmp/gem")
|
|
51
|
+
gem_root = File.expand_path("../..", __FILE__)
|
|
52
|
+
|
|
53
|
+
# Copy entire gem directory (including .git for gemspec)
|
|
54
|
+
copy_to_container(gem_root, dest)
|
|
55
|
+
|
|
56
|
+
# Remove Gemfile.lock to avoid bundler version mismatch
|
|
57
|
+
exec("rm", "-f", "#{dest}/Gemfile.lock")
|
|
58
|
+
|
|
59
|
+
# Fix permissions so test user can read the gem files
|
|
60
|
+
exec("chmod", "-R", "a+rX", dest)
|
|
61
|
+
|
|
62
|
+
# Fix git safe.directory for root and test user
|
|
63
|
+
exec("git", "config", "--global", "--add", "safe.directory", dest)
|
|
64
|
+
exec_as_user("git config --global --add safe.directory #{dest}")
|
|
65
|
+
|
|
66
|
+
dest
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def run_command(cmd)
|
|
72
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
|
73
|
+
raise "Command failed: #{cmd.join(' ')}\n#{stderr}" unless status.success?
|
|
74
|
+
stdout
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_image
|
|
78
|
+
run_command(["podman", "build", "-t", IMAGE_NAME, "-f", DOCKERFILE_PATH, File.dirname(DOCKERFILE_PATH)])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def wait_for_systemd(timeout: 30)
|
|
82
|
+
deadline = Time.now + timeout
|
|
83
|
+
loop do
|
|
84
|
+
stdout, _, status = exec("systemctl", "is-system-running")
|
|
85
|
+
state = stdout.strip
|
|
86
|
+
break if %w[running degraded].include?(state)
|
|
87
|
+
raise "Timed out waiting for systemd" if Time.now > deadline
|
|
88
|
+
sleep 0.5
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
RSpec.configure do |config|
|
|
95
|
+
config.before(:suite) do
|
|
96
|
+
SystemdContainer.start
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
config.after(:suite) do
|
|
100
|
+
SystemdContainer.stop
|
|
101
|
+
end
|
|
102
|
+
end
|
metadata
CHANGED
|
@@ -1,50 +1,71 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman-export-systemd_user
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Micah Geisel
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2026-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: foreman
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
|
-
- -
|
|
16
|
+
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.
|
|
18
|
+
version: 0.90.0
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
|
-
- -
|
|
23
|
+
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.
|
|
27
|
-
|
|
25
|
+
version: 0.90.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: Foreman export scripts for user-level systemd
|
|
28
41
|
email:
|
|
29
42
|
- micah@botandrose.com
|
|
30
|
-
executables:
|
|
43
|
+
executables:
|
|
44
|
+
- foreman
|
|
45
|
+
- setup
|
|
31
46
|
extensions: []
|
|
32
47
|
extra_rdoc_files: []
|
|
33
48
|
files:
|
|
49
|
+
- ".github/workflows/ci.yml"
|
|
34
50
|
- ".gitignore"
|
|
35
51
|
- Gemfile
|
|
36
52
|
- LICENSE
|
|
37
53
|
- README.md
|
|
38
54
|
- Rakefile
|
|
55
|
+
- bin/foreman
|
|
56
|
+
- bin/setup
|
|
39
57
|
- data/export/systemd_user/master.target.erb
|
|
40
58
|
- data/export/systemd_user/process.service.erb
|
|
59
|
+
- data/export/systemd_user/process_master.target.erb
|
|
41
60
|
- foreman-export-systemd_user.gemspec
|
|
42
61
|
- lib/foreman-export-systemd_user.rb
|
|
43
62
|
- lib/foreman/export/systemd_user.rb
|
|
63
|
+
- spec/Dockerfile.systemd
|
|
64
|
+
- spec/foreman/export/systemd_user_spec.rb
|
|
65
|
+
- spec/spec_helper.rb
|
|
44
66
|
homepage: http://github.com/botandrose/foreman-export-systemd_user
|
|
45
67
|
licenses: []
|
|
46
68
|
metadata: {}
|
|
47
|
-
post_install_message:
|
|
48
69
|
rdoc_options: []
|
|
49
70
|
require_paths:
|
|
50
71
|
- lib
|
|
@@ -59,8 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
59
80
|
- !ruby/object:Gem::Version
|
|
60
81
|
version: '0'
|
|
61
82
|
requirements: []
|
|
62
|
-
rubygems_version: 3.
|
|
63
|
-
signing_key:
|
|
83
|
+
rubygems_version: 3.6.2
|
|
64
84
|
specification_version: 4
|
|
65
|
-
summary:
|
|
85
|
+
summary: Foreman export scripts for user-level systemd
|
|
66
86
|
test_files: []
|