service: add basic cron support
This commit is contained in:
parent
e482a9f03d
commit
302975829d
@ -145,10 +145,8 @@ module Homebrew
|
|||||||
case T.unsafe(value)
|
case T.unsafe(value)
|
||||||
when nil
|
when nil
|
||||||
@run_type
|
@run_type
|
||||||
when :immediate, :interval
|
when :immediate, :interval, :cron
|
||||||
@run_type = value
|
@run_type = value
|
||||||
when :cron
|
|
||||||
raise TypeError, "Service#run_type does not support cron"
|
|
||||||
when Symbol
|
when Symbol
|
||||||
raise TypeError, "Service#run_type allows: '#{RUN_TYPE_IMMEDIATE}'/'#{RUN_TYPE_INTERVAL}'/'#{RUN_TYPE_CRON}'"
|
raise TypeError, "Service#run_type allows: '#{RUN_TYPE_IMMEDIATE}'/'#{RUN_TYPE_INTERVAL}'/'#{RUN_TYPE_CRON}'"
|
||||||
else
|
else
|
||||||
@ -168,6 +166,64 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(value: T.nilable(String)).returns(T.nilable(Hash)) }
|
||||||
|
def cron(value = nil)
|
||||||
|
case T.unsafe(value)
|
||||||
|
when nil
|
||||||
|
@cron
|
||||||
|
when String
|
||||||
|
@cron = parse_cron(T.must(value))
|
||||||
|
else
|
||||||
|
raise TypeError, "Service#cron expects a String"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T::Hash[Symbol, T.any(Integer, String)]) }
|
||||||
|
def default_cron_values
|
||||||
|
{
|
||||||
|
Month: "*",
|
||||||
|
Day: "*",
|
||||||
|
Weekday: "*",
|
||||||
|
Hour: "*",
|
||||||
|
Minute: "*",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(cron_statement: String).returns(T::Hash[Symbol, T.any(Integer, String)]) }
|
||||||
|
def parse_cron(cron_statement)
|
||||||
|
parsed = default_cron_values
|
||||||
|
|
||||||
|
case cron_statement
|
||||||
|
when "@hourly"
|
||||||
|
parsed[:Minute] = 0
|
||||||
|
when "@daily"
|
||||||
|
parsed[:Minute] = 0
|
||||||
|
parsed[:Hour] = 0
|
||||||
|
when "@weekly"
|
||||||
|
parsed[:Minute] = 0
|
||||||
|
parsed[:Hour] = 0
|
||||||
|
parsed[:Weekday] = 0
|
||||||
|
when "@monthly"
|
||||||
|
parsed[:Minute] = 0
|
||||||
|
parsed[:Hour] = 0
|
||||||
|
parsed[:Day] = 1
|
||||||
|
when "@yearly", "@annually"
|
||||||
|
parsed[:Minute] = 0
|
||||||
|
parsed[:Hour] = 0
|
||||||
|
parsed[:Day] = 1
|
||||||
|
parsed[:Month] = 1
|
||||||
|
else
|
||||||
|
cron_parts = cron_statement.split
|
||||||
|
raise TypeError, "Service#parse_cron expects a valid cron syntax" if cron_parts.length != 5
|
||||||
|
|
||||||
|
[:Minute, :Hour, :Day, :Month, :Weekday].each_with_index do |selector, index|
|
||||||
|
parsed[selector] = Integer(cron_parts.fetch(index)) if cron_parts.fetch(index) != "*"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
parsed
|
||||||
|
end
|
||||||
|
|
||||||
sig { params(variables: T::Hash[String, String]).returns(T.nilable(T::Hash[String, String])) }
|
sig { params(variables: T::Hash[String, String]).returns(T.nilable(T::Hash[String, String])) }
|
||||||
def environment_variables(variables = {})
|
def environment_variables(variables = {})
|
||||||
case T.unsafe(variables)
|
case T.unsafe(variables)
|
||||||
@ -246,6 +302,10 @@ module Homebrew
|
|||||||
base[:StandardErrorPath] = @error_log_path if @error_log_path.present?
|
base[:StandardErrorPath] = @error_log_path if @error_log_path.present?
|
||||||
base[:EnvironmentVariables] = @environment_variables unless @environment_variables.empty?
|
base[:EnvironmentVariables] = @environment_variables unless @environment_variables.empty?
|
||||||
|
|
||||||
|
if @cron.present? && @run_type == RUN_TYPE_CRON
|
||||||
|
base[:StartCalendarInterval] = @cron.reject { |_, value| value == "*" }
|
||||||
|
end
|
||||||
|
|
||||||
base.to_plist
|
base.to_plist
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -295,9 +355,15 @@ module Homebrew
|
|||||||
|
|
||||||
instance_eval(&@service_block)
|
instance_eval(&@service_block)
|
||||||
options = []
|
options = []
|
||||||
options << "Persistent=true=" if @run_type == RUN_TYPE_CRON
|
options << "Persistent=true" if @run_type == RUN_TYPE_CRON
|
||||||
options << "OnUnitActiveSec=#{@interval}" if @run_type == RUN_TYPE_INTERVAL
|
options << "OnUnitActiveSec=#{@interval}" if @run_type == RUN_TYPE_INTERVAL
|
||||||
|
|
||||||
|
if @run_type == RUN_TYPE_CRON
|
||||||
|
minutes = @cron[:Minute] == "*" ? "*" : format("%02d", @cron[:Minute])
|
||||||
|
hours = @cron[:Hour] == "*" ? "*" : format("%02d", @cron[:Hour])
|
||||||
|
options << "OnCalendar=#{@cron[:Weekday]}-*-#{@cron[:Month]}-#{@cron[:Day]} #{hours}:#{minutes}:00"
|
||||||
|
end
|
||||||
|
|
||||||
timer + options.join("\n")
|
timer + options.join("\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -32,16 +32,20 @@ describe Homebrew::Service do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#run_type" do
|
describe "#process_type" do
|
||||||
it "throws for cron type" do
|
it "throws for unexpected type" do
|
||||||
f.class.service do
|
f.class.service do
|
||||||
run opt_bin/"beanstalkd"
|
run opt_bin/"beanstalkd"
|
||||||
run_type :cron
|
process_type :cow
|
||||||
end
|
end
|
||||||
|
|
||||||
expect { f.service.manual_command }.to raise_error TypeError, "Service#run_type does not support cron"
|
expect {
|
||||||
|
f.service.manual_command
|
||||||
|
}.to raise_error TypeError, "Service#process_type allows: 'background'/'standard'/'interactive'/'adaptive'"
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#run_type" do
|
||||||
it "throws for unexpected type" do
|
it "throws for unexpected type" do
|
||||||
f.class.service do
|
f.class.service do
|
||||||
run opt_bin/"beanstalkd"
|
run opt_bin/"beanstalkd"
|
||||||
@ -206,6 +210,40 @@ describe Homebrew::Service do
|
|||||||
EOS
|
EOS
|
||||||
expect(plist).to eq(plist_expect)
|
expect(plist).to eq(plist_expect)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns valid cron plist" do
|
||||||
|
f.class.service do
|
||||||
|
run opt_bin/"beanstalkd"
|
||||||
|
run_type :cron
|
||||||
|
cron "@daily"
|
||||||
|
end
|
||||||
|
|
||||||
|
plist = f.service.to_plist
|
||||||
|
plist_expect = <<~EOS
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
\t<key>Label</key>
|
||||||
|
\t<string>homebrew.mxcl.formula_name</string>
|
||||||
|
\t<key>ProgramArguments</key>
|
||||||
|
\t<array>
|
||||||
|
\t\t<string>#{HOMEBREW_PREFIX}/opt/formula_name/bin/beanstalkd</string>
|
||||||
|
\t</array>
|
||||||
|
\t<key>RunAtLoad</key>
|
||||||
|
\t<false/>
|
||||||
|
\t<key>StartCalendarInterval</key>
|
||||||
|
\t<dict>
|
||||||
|
\t\t<key>Hour</key>
|
||||||
|
\t\t<integer>0</integer>
|
||||||
|
\t\t<key>Minute</key>
|
||||||
|
\t\t<integer>0</integer>
|
||||||
|
\t</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOS
|
||||||
|
expect(plist).to eq(plist_expect)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#to_systemd_unit" do
|
describe "#to_systemd_unit" do
|
||||||
@ -314,6 +352,53 @@ describe Homebrew::Service do
|
|||||||
EOS
|
EOS
|
||||||
expect(unit).to eq(unit_expect)
|
expect(unit).to eq(unit_expect)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "throws on incomplete cron" do
|
||||||
|
f.class.service do
|
||||||
|
run opt_bin/"beanstalkd"
|
||||||
|
run_type :cron
|
||||||
|
cron "1 2 3 4"
|
||||||
|
end
|
||||||
|
|
||||||
|
expect {
|
||||||
|
f.service.to_systemd_timer
|
||||||
|
}.to raise_error TypeError, "Service#parse_cron expects a valid cron syntax"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns valid cron timers" do
|
||||||
|
styles = {
|
||||||
|
"@hourly": "*-*-*-* *:00:00",
|
||||||
|
"@daily": "*-*-*-* 00:00:00",
|
||||||
|
"@weekly": "0-*-*-* 00:00:00",
|
||||||
|
"@monthly": "*-*-*-1 00:00:00",
|
||||||
|
"@yearly": "*-*-1-1 00:00:00",
|
||||||
|
"@annually": "*-*-1-1 00:00:00",
|
||||||
|
"5 5 5 5 5": "5-*-5-5 05:05:00",
|
||||||
|
}
|
||||||
|
|
||||||
|
styles.each do |cron, calendar|
|
||||||
|
f.class.service do
|
||||||
|
run opt_bin/"beanstalkd"
|
||||||
|
run_type :cron
|
||||||
|
cron cron.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
unit = f.service.to_systemd_timer
|
||||||
|
unit_expect = <<~EOS
|
||||||
|
[Unit]
|
||||||
|
Description=Homebrew generated timer for formula_name
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
Unit=homebrew.formula_name
|
||||||
|
Persistent=true
|
||||||
|
OnCalendar=#{calendar}
|
||||||
|
EOS
|
||||||
|
expect(unit).to eq(unit_expect.chomp)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#timed?" do
|
describe "#timed?" do
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user