service: add basic cron support
This commit is contained in:
		
							parent
							
								
									e482a9f03d
								
							
						
					
					
						commit
						302975829d
					
				| @ -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
	 Sean Molenaar
						Sean Molenaar