daemontools 0.2.7 → 0.2.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c22463b5a826586d0334a8f8d42f3e9d5dca647c5075073e3039770be063e515
4
- data.tar.gz: a9d89cdc0cb3d1bbdf0bd81dc2261a31a3d87fa7f1cec48d67ee7780a5fe5aa8
3
+ metadata.gz: 0406a7ec3beb2cb50e6103fc504b6c245f0051a59404d24832fa10fd3f60af5b
4
+ data.tar.gz: e850a26c07581ca728958072af3a4649b856286ac7756cc5c080adef0da55343
5
5
  SHA512:
6
- metadata.gz: 71c1a4bf680ac3cdef9be0767e3eabb7e2314fd4e3874c0dced95b028683b512df8c8c968b60d7d71e052fd397658fce097acd44bf8918506c7e9c9a3feb2200
7
- data.tar.gz: aed7e1991483a5f2f521f4c9cfe93af72c3e274710b172dd98baf9b80ef08aba74add43192e4947b4c8e7a392b8bb10ff96bb78007ee10a5bfcbe95df5477bd1
6
+ metadata.gz: 3947df53ae94a0f59d23e661ea636c31c45a293ad9c2fae5da51a5c0bb4d3d9c4553f724c3400f1c483a88065d2e735a5e365550b11445b1ffcfa45c8b7f8962
7
+ data.tar.gz: 1a71473ab9fbde2e778d98133fc99d207782d9ce3409d7494d5713cffddac408b2f66fcb5161e1212fdb9730d5be0e0348e718a6848457091524591ef63868a1
@@ -0,0 +1,194 @@
1
+ module Daemontools
2
+ class Service
3
+
4
+ CACHED_SERVICES = {}
5
+
6
+ def self.[](name)
7
+ CACHED_SERVICES[name] ||= new(name)
8
+ end
9
+
10
+ def initialize(name)
11
+ @name = name
12
+ @path = "#{Daemontools.svc_root}/#{name}"
13
+ @log_path = "#{@path}/log"
14
+ end
15
+
16
+ # Actions
17
+
18
+ def add(command, options)
19
+ apply_options(command: command, **options)
20
+
21
+ Dir.exist?(@path) ? stop : Dir.mkdir(@path)
22
+ Dir.mkdir(@log_path) unless Dir.exist?(@log_path)
23
+
24
+ File.open("#{@path}/down", 'w') { |f| f.write('') }
25
+ File.open("#{@log_path}/down", 'w') { |f| f.write('') }
26
+ File.open("#{@log_path}/run", 'w', 0o755) { |f| f.write(run_template('log.erb')) }
27
+ File.open("#{@path}/run", 'w', 0o755) { |f| f.write(run_template('run.erb')) }
28
+
29
+ wait_start(options[:wait_timeout]) unless options[:not_wait]
30
+
31
+ true
32
+ end
33
+
34
+ def delete(rm_cmd)
35
+ return false unless check_service_exists(false)
36
+
37
+ stop
38
+ sleep 0.3
39
+ cmd = rm_cmd.nil? ? "sudo rm -rf #{@path} 2>&1" : "#{rm_cmd} #{@path}"
40
+ r = `#{cmd}`
41
+ raise r if $?.exitstatus != 0
42
+
43
+ CACHED_SERVICES.delete(@name)
44
+ true
45
+ end
46
+
47
+ def stop
48
+ run_svc('d')
49
+ end
50
+
51
+ def start
52
+ run_svc('u')
53
+ end
54
+
55
+ def restart
56
+ run_svc('t')
57
+ end
58
+
59
+ # Statuses
60
+
61
+ def status
62
+ check_service_exists
63
+ r = `sudo svstat #{@path} 2>&1`
64
+ raise r if $?.exitstatus != 0
65
+ raise 'Unknown status' unless r.match(/.*?:\s*(\S+).*\s(\d+) seconds.*/)
66
+
67
+ [::Regexp.last_match(1), ::Regexp.last_match(2).to_i]
68
+ end
69
+
70
+ def up?
71
+ status[0] == 'up'
72
+ end
73
+
74
+ def down?
75
+ status[0] == 'down'
76
+ end
77
+
78
+ def check_service_exists(raise_error = true)
79
+ exists = Dir.exist?(@path)
80
+ raise_error && !exists ? raise("Service #{@name} not exists") : exists
81
+ end
82
+
83
+ # Run States
84
+
85
+ def run_status
86
+ check_service_exists
87
+ File.exist?("#{@path}/down") ? 'down' : 'up'
88
+ end
89
+
90
+ def run_status_up?
91
+ run_status == 'up'
92
+ end
93
+
94
+ def run_status_down?
95
+ run_status == 'down'
96
+ end
97
+
98
+ def run_status_up!
99
+ File.delete("#{@path}/down")
100
+ File.delete("#{@log_path}/down") if Dir.exist?(@log_path)
101
+ true
102
+ end
103
+
104
+ def run_status_down!
105
+ check_service_exists
106
+ File.open("#{@path}/down", 'w') { |f| f.write('') }
107
+ File.open("#{@log_path}/down", 'w') { |f| f.write('') } if Dir.exist?(@log_path)
108
+
109
+ true
110
+ end
111
+
112
+ # Tmp Actions
113
+
114
+ def add_empty
115
+ Dir.mkdir(@path) unless Dir.exist?(@path)
116
+ File.open("#{@path}/down", 'w') { |f| f.write('') }
117
+
118
+ wait_start(10)
119
+
120
+ File.delete("#{@path}/down")
121
+ stop
122
+
123
+ true
124
+ end
125
+
126
+ def add_empty_tmp
127
+ path = "#{Daemontools.tmp_root}/daemontools_service_#{@name}"
128
+ Dir.mkdir(path) unless Dir.exist?(path)
129
+
130
+ true
131
+ end
132
+
133
+ def move_tmp
134
+ tmp_path = "#{Daemontools.tmp_root}/daemontools_service_#{@name}"
135
+ svc_path = @path
136
+
137
+ r = `mv #{tmp_path} #{svc_path}`
138
+ raise r if $?.exitstatus != 0
139
+ raise r unless r.empty?
140
+
141
+ wait_start(10)
142
+
143
+ true
144
+ end
145
+
146
+ def tmp_exists?
147
+ Dir.exist?("#{Daemontools.tmp_root}/daemontools_service_#{@name}")
148
+ end
149
+
150
+ private
151
+
152
+ def run_svc(command)
153
+ check_service_exists
154
+ r = `sudo svc -#{command} #{@path} 2>&1`
155
+ raise r if $?.exitstatus != 0
156
+ raise r unless r.empty?
157
+
158
+ return true unless Dir.exist?(@log_path)
159
+
160
+ r = `sudo svc -#{command} #{@log_path} 2>&1`
161
+ raise r if $?.exitstatus != 0
162
+ raise r unless r.empty?
163
+
164
+ true
165
+ end
166
+
167
+ def run_template(template_name)
168
+ @user = Etc.getpwuid(Process.uid).name
169
+ template_path = "#{__dir__}/../templates/#{template_name}"
170
+ ERB.new(File.read(template_path)).result(binding)
171
+ end
172
+
173
+ def apply_options(**options)
174
+ @command = options[:command]
175
+ @log_dir = options[:log_dir] || "#{Daemontools.log_root}/#{@name}"
176
+ @pre_command = options[:pre_command]
177
+ @sleep = options[:sleep] || 3
178
+ @change_user_command = options[:change_user_command]
179
+ @ulimit = options[:ulimit]
180
+ @write_time = options[:write_time]
181
+ end
182
+
183
+ def wait_start(wait_timeout = nil)
184
+ wait_timeout ||= 10
185
+ now = Time.now.to_f
186
+
187
+ while `sudo svstat #{@path} 2>&1`.match(/unable to open/i)
188
+ raise 'Timeout wait for svc add service' if Time.now.to_f - now > wait_timeout
189
+
190
+ sleep 0.1
191
+ end
192
+ end
193
+ end
194
+ end
@@ -1,3 +1,3 @@
1
1
  module Daemontools
2
- VERSION = "0.2.7"
2
+ VERSION = "0.2.10"
3
3
  end
data/lib/daemontools.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'daemontools/version'
2
+ require 'daemontools/service'
2
3
  require 'daemontools/service_builder'
3
4
  require 'daemontools/service_remover'
4
5
  require 'etc'
@@ -12,167 +13,87 @@ module Daemontools
12
13
  @log_root = '/var/log/svc'
13
14
  @tmp_root = '/tmp'
14
15
 
15
- def self.exists?(name)
16
- check_service_exists(name, false)
17
- end
18
-
19
- def self.tmp_exists?(name)
20
- Dir.exists?("#{@tmp_root}/daemontools_service_#{name}")
21
- end
16
+ # Actions
22
17
 
23
- def self.status(name)
24
- check_service_exists(name)
25
- r = `sudo svstat #{@path} 2>&1`
26
- raise r if $?.exitstatus != 0
27
- raise "Unknown status" unless r.match(/.*?:\s*(\S+).*\s(\d+) seconds.*/)
28
- [$1, $2.to_i]
18
+ def self.add(name, command, options = {})
19
+ Service[name].add(command, options)
29
20
  end
30
21
 
31
- def self.up?(name)
32
- status(name)[0] == "up"
22
+ def self.delete(name, rm_cmd = nil)
23
+ Service[name].delete(rm_cmd)
33
24
  end
34
25
 
35
- def self.down?(name)
36
- status(name)[0] == "down"
26
+ def self.start(name)
27
+ Service[name].start
37
28
  end
38
29
 
39
30
  def self.stop(name)
40
- run_svc(name, 'd')
41
- end
42
-
43
- def self.start(name)
44
- run_svc(name, 'u')
31
+ Service[name].stop
45
32
  end
46
33
 
47
34
  def self.restart(name)
48
- run_svc(name, 't')
35
+ Service[name].restart
49
36
  end
50
37
 
51
- def self.add_empty(name)
52
- path = "#{@svc_root}/#{name}"
53
- Dir.mkdir(path) unless Dir.exists?(path)
54
- File.open("#{path}/down", 'w') {|f| f.write('')}
55
- now = Time.now.to_f
56
- while `sudo svstat #{path} 2>&1`.match(/unable to open/i)
57
- raise "Timeout wait for svc add service" if Time.now.to_f - now > 10
58
- sleep 0.1
59
- end
60
- File.delete("#{path}/down")
61
- stop(name)
62
- true
63
- end
38
+ # Statuses
64
39
 
65
- def self.add_empty_tmp(name)
66
- path = "#{@tmp_root}/daemontools_service_#{name}"
67
- Dir.mkdir(path) unless Dir.exists?(path)
68
- true
40
+ def self.status(name)
41
+ Service[name].status
69
42
  end
70
43
 
71
- def self.move_tmp(name)
72
- tmp_path = "#{@tmp_root}/daemontools_service_#{name}"
73
- svc_path = "#{@svc_root}/#{name}"
74
-
75
- r = `mv #{tmp_path} #{svc_path}`
76
- raise r if $?.exitstatus != 0
77
- raise r if ! r.empty?
78
-
79
- now = Time.now.to_f
80
- while `sudo svstat #{svc_path} 2>&1`.match(/unable to open/i)
81
- raise "Timeout wait for svc add service" if Time.now.to_f - now > 10
82
- sleep 0.1
83
- end
44
+ def self.up?(name)
45
+ Service[name].up?
46
+ end
84
47
 
85
- true
48
+ def self.down?(name)
49
+ Service[name].down?
86
50
  end
87
51
 
88
- def self.add(name, command, options = {})
89
- @name = name
90
- @command = command
91
- @log_dir = options[:log_dir] || "#{@log_root}/#{@name}"
92
- @pre_command = options[:pre_command]
93
- @sleep = options[:sleep] || 3
94
- @path = "#{@svc_root}/#{name}"
95
- @change_user_command = options[:change_user_command]
96
- @ulimit = options[:ulimit]
97
- @write_time = options[:write_time]
98
-
99
- if Dir.exists?(@path)
100
- stop(name)
101
- else
102
- Dir.mkdir(@path)
103
- end
104
- File.open("#{@path}/down", 'w') {|f| f.write('')}
105
- Dir.mkdir("#{@path}/log") unless Dir.exists?("#{@path}/log")
106
- File.open("#{@path}/log/run", 'w', 0755) {|f| f.write(run_template('log.erb'))}
107
- File.open("#{@path}/run", 'w', 0755) {|f| f.write(run_template('run.erb'))}
108
-
109
- unless options[:not_wait]
110
- wait_timeout = options[:wait_timeout] || 10
111
- now = Time.now.to_f
112
- while `sudo svstat #{@path} 2>&1`.match(/unable to open/i)
113
- raise "Timeout wait for svc add service" if Time.now.to_f - now > wait_timeout
114
- sleep 0.1
115
- end
116
- end
117
-
118
- true
52
+ def self.exists?(name)
53
+ Service[name].check_service_exists(false)
119
54
  end
120
55
 
121
- def self.delete(name, rm_cmd = nil)
122
- return false unless exists?(name)
123
- stop(name)
124
- cmd = rm_cmd.nil? ? "sudo rm -rf #{@path} 2>&1" : "#{rm_cmd} #{@path}"
125
- r = `#{cmd}`
126
- raise r if $?.exitstatus != 0
127
- true
56
+ def self.check_service_exists(name, raise_error = true)
57
+ Service[name].check_service_exists(raise_error)
128
58
  end
129
59
 
60
+ # Run States
61
+
130
62
  def self.run_status(name)
131
- check_service_exists(name)
132
- File.exists?("#{@path}/down") ? "down" : "up"
63
+ Service[name].run_status
133
64
  end
134
65
 
135
66
  def self.run_status_up?(name)
136
- run_status(name) == "up"
67
+ Service[name].run_status_up?
137
68
  end
138
69
 
139
70
  def self.run_status_down?(name)
140
- run_status(name) == "down"
71
+ Service[name].run_status_down?
141
72
  end
142
73
 
143
74
  def self.make_run_status_up(name)
144
- File.delete("#{@path}/down")
145
- true
75
+ Service[name].run_status_up!
146
76
  end
147
77
 
148
78
  def self.make_run_status_down(name)
149
- check_service_exists(name)
150
- File.open("#{@path}/down", 'w') {|f| f.write('')}
151
- true
79
+ Service[name].run_status_down!
152
80
  end
153
81
 
154
- private
82
+ # Tmp Actions
155
83
 
156
- def self.check_service_exists(name, raise_error = true)
157
- @path = "#{@svc_root}/#{name}"
158
- if raise_error
159
- raise "Service #{name} not exists" unless Dir.exists?(@path)
160
- else
161
- Dir.exists?(@path)
162
- end
163
- end
164
-
165
- def self.run_svc(name, command)
166
- check_service_exists(name)
167
- r = `sudo svc -#{command} #{@path} 2>&1`
168
- raise r if $?.exitstatus != 0
169
- raise r if ! r.empty?
170
- true
171
- end
172
-
173
- def self.run_template(template_name)
174
- @user = Etc.getpwuid(Process.uid).name
175
- template_path = File.expand_path(File.dirname(__FILE__))+'/../templates/'+template_name
176
- ERB.new(File.read(template_path)).result(binding())
84
+ def self.add_empty(name)
85
+ Service[name].add_empty
86
+ end
87
+
88
+ def self.add_empty_tmp(name)
89
+ Service[name].add_empty_tmp
90
+ end
91
+
92
+ def self.move_tmp(name)
93
+ Service[name].move_tmp
94
+ end
95
+
96
+ def self.tmp_exists?(name)
97
+ Service[name].tmp_exists?
177
98
  end
178
99
  end
@@ -0,0 +1,157 @@
1
+ require 'daemontools'
2
+
3
+ RSpec.describe Daemontools::Service do
4
+ let(:svc_root) { '/tmp/svc_root' }
5
+ let(:tmp_root) { '/tmp/tmp_root' }
6
+ let(:log_root) { '/tmp/log_root' }
7
+
8
+ before do
9
+ stub_const('Daemontools::Service::CACHED_SERVICES', {})
10
+
11
+ allow(Daemontools).to receive(:svc_root).and_return(svc_root)
12
+ allow(Daemontools).to receive(:tmp_root).and_return(tmp_root)
13
+ allow(Daemontools).to receive(:log_root).and_return(log_root)
14
+
15
+ FileUtils.mkdir_p(svc_root)
16
+ FileUtils.mkdir_p(tmp_root)
17
+ FileUtils.mkdir_p(log_root)
18
+ end
19
+
20
+ after do
21
+ FileUtils.rm_rf(svc_root)
22
+ FileUtils.rm_rf(tmp_root)
23
+ FileUtils.rm_rf(log_root)
24
+ end
25
+
26
+ describe '.[]' do
27
+ it 'caches and returns the same instance for the same name' do
28
+ s1 = described_class['test']
29
+ s2 = described_class['test']
30
+ expect(s1).to be_a(described_class)
31
+ expect(s1).to equal(s2)
32
+ end
33
+ end
34
+
35
+ describe '#check_service_exists' do
36
+ let(:service) { described_class.new('svc1') }
37
+
38
+ it 'returns false when the directory does not exist' do
39
+ expect(service.check_service_exists(false)).to be false
40
+ end
41
+
42
+ it 'raises an error when directory is missing and raise_error=true' do
43
+ expect { service.check_service_exists(true) }.to raise_error(/not exists/)
44
+ end
45
+
46
+ it 'returns true when the directory exists' do
47
+ FileUtils.mkdir_p("#{svc_root}/svc1")
48
+ expect(service.check_service_exists(false)).to be true
49
+ end
50
+ end
51
+
52
+ describe '#delete' do
53
+ let(:service) { described_class.new('svc1') }
54
+
55
+ before do
56
+ FileUtils.mkdir_p("#{svc_root}/svc1")
57
+ allow(service).to receive(:stop).and_return(true)
58
+ allow(service).to receive(:`).and_return('')
59
+ allow($?).to receive(:exitstatus).and_return(0)
60
+ Daemontools::Service::CACHED_SERVICES['svc1'] = service
61
+ end
62
+
63
+ it 'removes the service from cache and returns true' do
64
+ expect(service.delete(nil)).to be true
65
+ expect(Daemontools::Service::CACHED_SERVICES).not_to have_key('svc1')
66
+ end
67
+
68
+ it 'returns false when service does not exist' do
69
+ FileUtils.rm_rf("#{svc_root}/svc1")
70
+ expect(service.delete(nil)).to be false
71
+ end
72
+
73
+ it 'raises an error when command exit status is non‑zero' do
74
+ allow($?).to receive(:exitstatus).and_return(1)
75
+ expect { service.delete(nil) }.to raise_error(RuntimeError)
76
+ end
77
+ end
78
+
79
+ describe '#status' do
80
+ let(:service) { described_class.new('svc2') }
81
+ let(:output) { 'svc2: up (pid 1234) 5 seconds' }
82
+
83
+ before do
84
+ FileUtils.mkdir_p("#{svc_root}/svc2")
85
+ allow(service).to receive(:`).and_return(output)
86
+ allow($?).to receive(:exitstatus).and_return(0)
87
+ end
88
+
89
+ it 'returns [status, seconds] for valid svstat output' do
90
+ expect(service.status).to eq(%w[up 5].tap { |a| a[1] = 5 })
91
+ end
92
+
93
+ it 'raises "Unknown status" for unexpected output' do
94
+ allow(service).to receive(:`).and_return('something weird')
95
+ expect { service.status }.to raise_error('Unknown status')
96
+ end
97
+
98
+ it 'raises an error when command fails' do
99
+ allow($?).to receive(:exitstatus).and_return(1)
100
+ expect { service.status }.to raise_error(/svc2/)
101
+ end
102
+ end
103
+
104
+ describe '#run_status_*' do
105
+ let(:service) { described_class.new('svc3') }
106
+
107
+ before do
108
+ FileUtils.mkdir_p("#{svc_root}/svc3")
109
+ end
110
+
111
+ it 'returns "up" when no down file exists' do
112
+ expect(service.run_status).to eq('up')
113
+ end
114
+
115
+ it 'returns "down" when down file exists' do
116
+ FileUtils.touch("#{svc_root}/svc3/down")
117
+ expect(service.run_status).to eq('down')
118
+ end
119
+
120
+ it '#run_status_up! removes down files' do
121
+ FileUtils.mkdir_p("#{svc_root}/svc3/log")
122
+ FileUtils.touch("#{svc_root}/svc3/down")
123
+ FileUtils.touch("#{svc_root}/svc3/log/down")
124
+ service.run_status_up!
125
+ expect(File).not_to exist("#{svc_root}/svc3/down")
126
+ expect(File).not_to exist("#{svc_root}/svc3/log/down")
127
+ end
128
+
129
+ it '#run_status_down! creates down files' do
130
+ FileUtils.mkdir_p("#{svc_root}/svc3/log")
131
+ service.run_status_down!
132
+ expect(File).to exist("#{svc_root}/svc3/down")
133
+ expect(File).to exist("#{svc_root}/svc3/log/down")
134
+ end
135
+ end
136
+
137
+ describe '#tmp_exists?' do
138
+ let(:service) { described_class.new('svc_tmp') }
139
+
140
+ it 'returns true when temporary directory exists' do
141
+ FileUtils.mkdir_p("#{tmp_root}/daemontools_service_svc_tmp")
142
+ expect(service.tmp_exists?).to be true
143
+ end
144
+
145
+ it 'returns false when temporary directory is missing' do
146
+ FileUtils.rm_rf("#{tmp_root}/daemontools_service_svc_tmp")
147
+ expect(service.tmp_exists?).to be false
148
+ end
149
+ end
150
+
151
+ describe '.CACHED_SERVICES' do
152
+ it 'holds created instances' do
153
+ s1 = described_class['foo']
154
+ expect(Daemontools::Service::CACHED_SERVICES['foo']).to eq(s1)
155
+ end
156
+ end
157
+ end
data/templates/rvm.erb CHANGED
@@ -1,3 +1,15 @@
1
- /bin/bash -l -c '<%= @command.gsub('&&', "\n") %> 2>&1 &
2
- trap "kill $!" exit INT TERM
3
- wait'
1
+ /bin/bash -l -c '
2
+ <%= @command.gsub('&&', "\n") %> 2>&1 &
3
+ pid=$!
4
+ pgid=$(ps -o pgid= -p $pid | tr -d " ")
5
+
6
+ trap "
7
+ echo \"Sending SIGTERM to main process PID \$pid\"
8
+ kill -TERM \$pid
9
+ sleep 0.5
10
+ echo \"Sending SIGKILL to process group PGID \$pgid\"
11
+ kill -KILL -\$pgid
12
+ " EXIT INT TERM
13
+
14
+ wait $pid
15
+ '
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daemontools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - sh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-01 00:00:00.000000000 Z
11
+ date: 2026-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -75,11 +75,13 @@ files:
75
75
  - fixtures/stubs/service_remover_stub.rb
76
76
  - lib/daemontools.rb
77
77
  - lib/daemontools/capistrano.rb
78
+ - lib/daemontools/service.rb
78
79
  - lib/daemontools/service_builder.rb
79
80
  - lib/daemontools/service_remover.rb
80
81
  - lib/daemontools/version.rb
81
82
  - spec/builder_spec.rb
82
83
  - spec/service_delete_spec.rb
84
+ - spec/service_spec.rb
83
85
  - spec/spec_helper.rb
84
86
  - templates/log.erb
85
87
  - templates/run.erb
@@ -109,4 +111,5 @@ summary: Distributed storage
109
111
  test_files:
110
112
  - spec/builder_spec.rb
111
113
  - spec/service_delete_spec.rb
114
+ - spec/service_spec.rb
112
115
  - spec/spec_helper.rb