Unverified Commit a285ef6e authored by Sean Molenaar's avatar Sean Molenaar Committed by GitHub
Browse files

Merge pull request #12462 from SMillerDev/feature/service/cron

service: add basic cron support
parents 8f858331 30297582
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment