Merge pull request #12462 from SMillerDev/feature/service/cron
service: add basic cron support
This commit is contained in:
commit
a285ef6eac
@ -145,10 +145,8 @@ module Homebrew
|
||||
case T.unsafe(value)
|
||||
when nil
|
||||
@run_type
|
||||
when :immediate, :interval
|
||||
when :immediate, :interval, :cron
|
||||
@run_type = value
|
||||
when :cron
|
||||
raise TypeError, "Service#run_type does not support cron"
|
||||
when Symbol
|
||||
raise TypeError, "Service#run_type allows: '#{RUN_TYPE_IMMEDIATE}'/'#{RUN_TYPE_INTERVAL}'/'#{RUN_TYPE_CRON}'"
|
||||
else
|
||||
@ -168,6 +166,64 @@ module Homebrew
|
||||
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])) }
|
||||
def environment_variables(variables = {})
|
||||
case T.unsafe(variables)
|
||||
@ -246,6 +302,10 @@ module Homebrew
|
||||
base[:StandardErrorPath] = @error_log_path if @error_log_path.present?
|
||||
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
|
||||
end
|
||||
|
||||
@ -295,9 +355,15 @@ module Homebrew
|
||||
|
||||
instance_eval(&@service_block)
|
||||
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
|
||||
|
||||
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")
|
||||
end
|
||||
end
|
||||
|
@ -32,16 +32,20 @@ describe Homebrew::Service do
|
||||
end
|
||||
end
|
||||
|
||||
describe "#run_type" do
|
||||
it "throws for cron type" do
|
||||
describe "#process_type" do
|
||||
it "throws for unexpected type" do
|
||||
f.class.service do
|
||||
run opt_bin/"beanstalkd"
|
||||
run_type :cron
|
||||
process_type :cow
|
||||
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
|
||||
|
||||
describe "#run_type" do
|
||||
it "throws for unexpected type" do
|
||||
f.class.service do
|
||||
run opt_bin/"beanstalkd"
|
||||
@ -206,6 +210,40 @@ describe Homebrew::Service do
|
||||
EOS
|
||||
expect(plist).to eq(plist_expect)
|
||||
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
|
||||
|
||||
describe "#to_systemd_unit" do
|
||||
@ -314,6 +352,53 @@ describe Homebrew::Service do
|
||||
EOS
|
||||
expect(unit).to eq(unit_expect)
|
||||
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
|
||||
|
||||
describe "#timed?" do
|
||||
|
Loading…
x
Reference in New Issue
Block a user