ruby_raider 4.0.1 → 4.1.0
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 +4 -4
- data/README.md +59 -1
- data/RELEASE.md +12 -0
- data/lib/commands/flow_commands.rb +104 -0
- data/lib/generators/templates/flows/flow_helper.tt +42 -0
- data/lib/generators/templates/flows/run.tt +332 -0
- data/lib/ruby_raider.rb +16 -0
- data/lib/utilities/flows/next_run.rb +61 -0
- data/lib/utilities/flows/notifier.rb +76 -0
- data/lib/utilities/flows/os/command_runner.rb +73 -0
- data/lib/utilities/flows/os/cron.rb +137 -0
- data/lib/utilities/flows/os/launchd.rb +164 -0
- data/lib/utilities/flows/os/linux.rb +63 -0
- data/lib/utilities/flows/os/systemd.rb +178 -0
- data/lib/utilities/flows/os/task_scheduler.rb +179 -0
- data/lib/utilities/flows/project.rb +170 -0
- data/lib/utilities/flows/run_log.rb +61 -0
- data/lib/utilities/flows/runner.rb +206 -0
- data/lib/utilities/flows/schedule.rb +221 -0
- data/lib/utilities/flows/schedule_store.rb +135 -0
- data/lib/utilities/flows/scheduler.rb +187 -0
- data/lib/version +1 -1
- data/sig/commands/flow_commands.rbs +13 -0
- data/sig/ruby_raider.rbs +2 -0
- data/sig/utilities/flows/next_run.rbs +15 -0
- data/sig/utilities/flows/notifier.rbs +29 -0
- data/sig/utilities/flows/os/command_runner.rbs +27 -0
- data/sig/utilities/flows/os/cron.rbs +34 -0
- data/sig/utilities/flows/os/launchd.rbs +45 -0
- data/sig/utilities/flows/os/linux.rbs +27 -0
- data/sig/utilities/flows/os/systemd.rbs +43 -0
- data/sig/utilities/flows/os/task_scheduler.rbs +53 -0
- data/sig/utilities/flows/project.rbs +46 -0
- data/sig/utilities/flows/run_log.rbs +22 -0
- data/sig/utilities/flows/runner.rbs +35 -0
- data/sig/utilities/flows/schedule.rbs +61 -0
- data/sig/utilities/flows/schedule_store.rbs +34 -0
- data/sig/utilities/flows/scheduler.rbs +42 -0
- data/spec/commands/flow_commands_spec.rb +128 -0
- data/spec/generators/flow_helper_spec.rb +40 -0
- data/spec/integration/content/gemfile_content_spec.rb +2 -2
- data/spec/integration/end_to_end_spec.rb +63 -0
- data/spec/integration/flows/flow_project.rb +62 -0
- data/spec/integration/flows/run_command_spec.rb +91 -0
- data/spec/integration/flows/run_lifecycle_spec.rb +383 -0
- data/spec/utilities/flows/next_run_spec.rb +119 -0
- data/spec/utilities/flows/notifier_spec.rb +121 -0
- data/spec/utilities/flows/os/command_runner_spec.rb +56 -0
- data/spec/utilities/flows/os/cron_spec.rb +287 -0
- data/spec/utilities/flows/os/launchd_spec.rb +293 -0
- data/spec/utilities/flows/os/linux_spec.rb +149 -0
- data/spec/utilities/flows/os/systemd_spec.rb +329 -0
- data/spec/utilities/flows/os/task_scheduler_spec.rb +505 -0
- data/spec/utilities/flows/project_spec.rb +178 -0
- data/spec/utilities/flows/run_log_spec.rb +47 -0
- data/spec/utilities/flows/runner_spec.rb +313 -0
- data/spec/utilities/flows/schedule_spec.rb +233 -0
- data/spec/utilities/flows/schedule_store_spec.rb +137 -0
- data/spec/utilities/flows/scheduler_spec.rb +329 -0
- metadata +53 -2
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'rbconfig'
|
|
6
|
+
require_relative 'next_run'
|
|
7
|
+
require_relative 'run_log'
|
|
8
|
+
require_relative 'schedule'
|
|
9
|
+
require_relative 'schedule_store'
|
|
10
|
+
require_relative 'os/command_runner'
|
|
11
|
+
require_relative 'os/launchd'
|
|
12
|
+
require_relative 'os/task_scheduler'
|
|
13
|
+
require_relative 'os/linux'
|
|
14
|
+
|
|
15
|
+
module RubyRaider
|
|
16
|
+
module Flows
|
|
17
|
+
# `raider flow schedule | unschedule | schedules`: keeps the OS scheduler entries and
|
|
18
|
+
# .raider/flows/schedules.yml in step. The OS entry is installed first and the yml written after,
|
|
19
|
+
# so the yml never names an entry the OS scheduler refused.
|
|
20
|
+
# :reek:TooManyInstanceVariables { max_instance_variables: 6 } -- the project, its store, the adapter and the injected streams and clock
|
|
21
|
+
class Scheduler
|
|
22
|
+
NOT_SET_UP = 'Flows are not set up in this project: run raider flow init'
|
|
23
|
+
RUBY_NOT_FOUND = 'Ruby not found: re-schedule'
|
|
24
|
+
FLOW_NOT_FOUND = 'flow not found: unschedule'
|
|
25
|
+
LAST = %w[status finished_at trigger failed_step results_dir].freeze
|
|
26
|
+
TIME = '%a %-d %b %H:%M'
|
|
27
|
+
|
|
28
|
+
# The OS scheduler for this machine; only Linux asks the system (is the user's systemd there?)
|
|
29
|
+
def self.adapter(runner: Os::CommandRunner.new, host_os: RbConfig::CONFIG['host_os'])
|
|
30
|
+
case host_os
|
|
31
|
+
when /darwin/ then Os::Launchd.new(runner: runner)
|
|
32
|
+
when /mswin|mingw|cygwin/ then Os::TaskScheduler.new(runner: runner)
|
|
33
|
+
when /linux/ then Os::Linux.pick(runner)
|
|
34
|
+
else raise Error, "Scheduling flows is not supported on #{host_os}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# adapter: nil picks the OS scheduler on first use, so a command refused earlier never touches it
|
|
39
|
+
def initialize(project, adapter: nil, store: ScheduleStore.new(project), out: $stdout, err: $stderr, now: -> { Time.now })
|
|
40
|
+
@project = project
|
|
41
|
+
@adapter = adapter
|
|
42
|
+
@store = store
|
|
43
|
+
@out = out
|
|
44
|
+
@err = err
|
|
45
|
+
@now = now
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def schedule(name, every:, at: nil, on: nil, retries: 0, inputs: [], notify: false)
|
|
49
|
+
@project.flow_file(name)
|
|
50
|
+
raise Error, NOT_SET_UP unless @project.set_up?
|
|
51
|
+
|
|
52
|
+
now = @now.call
|
|
53
|
+
schedule = Schedule.build(project: @project, name: name, every: every, at: at, on: on, retries: retries,
|
|
54
|
+
inputs: inputs, notify: notify, now: now)
|
|
55
|
+
flow_dir(name)
|
|
56
|
+
@store.writable!
|
|
57
|
+
old = @store.find(name)
|
|
58
|
+
adapter.install(schedule)
|
|
59
|
+
save(schedule)
|
|
60
|
+
prefix = old ? "Replaced the schedule for #{name}" : "Scheduled #{name}"
|
|
61
|
+
@out.puts("#{prefix}: #{rule(schedule)}. Next run #{NextRun.after(schedule, now).strftime(TIME)}.")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def unschedule(name)
|
|
65
|
+
raise Error, "Invalid flow name #{name.inspect}: use 1 to 60 of a-z, 0-9 and _ (not env)" unless Flows.valid_name?(name)
|
|
66
|
+
|
|
67
|
+
# The OS entry first: when the scheduler refuses, the yml entry stays to retry with.
|
|
68
|
+
# from_h is strict, so a stored entry's os_id is always this one.
|
|
69
|
+
label = adapter.label
|
|
70
|
+
was_there = adapter.remove(Schedule.os_id_for(@project.root, name))
|
|
71
|
+
removed = delete(name, label)
|
|
72
|
+
raise Error, "No schedule for #{name}" unless removed || was_there
|
|
73
|
+
|
|
74
|
+
@out.puts(was_there ? "Unscheduled #{name}" : "Unscheduled #{name} (it was already gone from #{label})")
|
|
75
|
+
return unless removed.is_a?(ScheduleStore::Removed)
|
|
76
|
+
|
|
77
|
+
@err.puts("The schedules.yml entry for #{name} could not be read: a #{label} entry made from a different project path may remain")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def schedules(json: false)
|
|
81
|
+
now = @now.call
|
|
82
|
+
listed = @store.all
|
|
83
|
+
rows = listed.map { |schedule| row(schedule, now) } + unlisted_rows(listed.map(&:name))
|
|
84
|
+
return @out.puts(JSON.generate(rows.map { |entry| entry[:json] })) if json
|
|
85
|
+
return @out.puts('No schedules yet: raider flow schedule <name> --every day --at 08:00') if rows.empty?
|
|
86
|
+
|
|
87
|
+
rows.each { |entry| @out.puts(Flows.printable(entry[:text])) }
|
|
88
|
+
note = adapter.respond_to?(:missed_runs_note) ? adapter.missed_runs_note : nil
|
|
89
|
+
@out.puts(note) if note
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def adapter = @adapter ||= Scheduler.adapter
|
|
95
|
+
|
|
96
|
+
# Writes the yml; when that fails the OS entry just installed is removed, so neither names the flow
|
|
97
|
+
def save(schedule)
|
|
98
|
+
@store.save(schedule)
|
|
99
|
+
rescue Error, SystemCallError => e
|
|
100
|
+
label = adapter.label
|
|
101
|
+
begin
|
|
102
|
+
adapter.remove(schedule.os_id)
|
|
103
|
+
rescue Error => remove_error
|
|
104
|
+
raise Error, "#{e.message} (and removing the #{label} entry failed: #{remove_error.message})"
|
|
105
|
+
end
|
|
106
|
+
raise Error, "#{e.message}: the #{label} entry was removed too, so #{schedule.name} is not scheduled"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# The OS entry is gone by now: say so when the yml can't be written
|
|
110
|
+
def delete(name, label)
|
|
111
|
+
@store.delete(name)
|
|
112
|
+
rescue Error, SystemCallError => e
|
|
113
|
+
raise Error, "#{e.message}: the #{label} entry was already removed, but schedules.yml still lists #{name}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# .raider/flows/<name>/ for scheduler.log: 0700 folders that are not links, as flows/support/run.rb makes them
|
|
117
|
+
def flow_dir(name)
|
|
118
|
+
[ScheduleStore::DIR, "#{ScheduleStore::DIR}/#{name}"].each { |relative| private_dir(relative) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def private_dir(relative)
|
|
122
|
+
dir = @project.path(relative)
|
|
123
|
+
FileUtils.mkdir_p(dir)
|
|
124
|
+
raise Error, "#{relative} must be a folder, not a link" unless File.lstat(dir).directory?
|
|
125
|
+
|
|
126
|
+
File.chmod(0o700, dir)
|
|
127
|
+
rescue SystemCallError => e
|
|
128
|
+
raise Error, "Could not make #{relative}: #{e.message}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def row(schedule, now)
|
|
132
|
+
name = schedule.name
|
|
133
|
+
next_run = NextRun.after(schedule, now)
|
|
134
|
+
state = os_state(schedule)
|
|
135
|
+
last = last_run(name)
|
|
136
|
+
json = { 'name' => name, 'every' => schedule.every, 'at' => schedule.at, 'on' => schedule.on,
|
|
137
|
+
'retries' => schedule.retries, 'inputs' => schedule.inputs, 'notify' => schedule.notify, 'next_run_at' => next_run.iso8601,
|
|
138
|
+
'os_state' => state, 'last' => last }
|
|
139
|
+
{ json: json, text: line(name, rule(schedule), "next #{next_run.strftime(TIME)}", last, state) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Flows the OS scheduler still runs for this project although schedules.yml has no entry for them
|
|
143
|
+
def unlisted_rows(listed)
|
|
144
|
+
(@project.flow_names - listed).filter_map do |name|
|
|
145
|
+
next unless present?(Schedule.os_id_for(@project.root, name))
|
|
146
|
+
|
|
147
|
+
state = 'missing from schedules.yml'
|
|
148
|
+
last = last_run(name)
|
|
149
|
+
json = { 'name' => name, 'every' => nil, 'at' => nil, 'on' => nil, 'retries' => nil, 'inputs' => nil, 'notify' => nil,
|
|
150
|
+
'next_run_at' => nil, 'os_state' => state, 'last' => last }
|
|
151
|
+
{ json: json, text: line(name, '-', '-', last, state) }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# A flow whose state can't be read is left out: only a listed schedule gets an unknown row
|
|
156
|
+
def present?(os_id)
|
|
157
|
+
adapter.present?(os_id)
|
|
158
|
+
rescue Error
|
|
159
|
+
false
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# The OS entry runs `<ruby> <bundle> ...`: ruby is executed, bundle is only read by it
|
|
163
|
+
def os_state(schedule)
|
|
164
|
+
ruby = schedule.ruby
|
|
165
|
+
bundle = schedule.bundle
|
|
166
|
+
return RUBY_NOT_FOUND unless File.file?(ruby) && File.executable?(ruby) && File.file?(bundle) && File.readable?(bundle)
|
|
167
|
+
return FLOW_NOT_FOUND unless File.file?(@project.path("flows/#{schedule.name}_flow.rb"))
|
|
168
|
+
|
|
169
|
+
adapter.present?(schedule.os_id) ? 'ok' : "missing from #{adapter.label}"
|
|
170
|
+
rescue Error => e
|
|
171
|
+
"unknown (#{e.message})"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def rule(schedule) = schedule.notify ? "#{schedule.rule_text} (notify)" : schedule.rule_text
|
|
175
|
+
|
|
176
|
+
def last_run(name)
|
|
177
|
+
last = RunLog.new(@project, name).last_result
|
|
178
|
+
last && LAST.to_h { |key| [key, last[key]] }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def line(name, rule, next_run, last, state)
|
|
182
|
+
result = last ? "#{last['status']} #{last['finished_at']}" : 'never run'
|
|
183
|
+
"#{name.ljust(30)} #{rule} #{next_run} #{result} #{state}"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
data/lib/version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0
|
|
1
|
+
4.1.0
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Type definitions for FlowCommands
|
|
2
|
+
class FlowCommands < Thor
|
|
3
|
+
def init: () -> void
|
|
4
|
+
def run_flow: (String name) -> void
|
|
5
|
+
def list: () -> void
|
|
6
|
+
def schedule: (String name) -> void
|
|
7
|
+
def unschedule: (String name) -> void
|
|
8
|
+
def schedules: () -> void
|
|
9
|
+
def project: () -> RubyRaider::Flows::Project
|
|
10
|
+
def scheduler: () -> RubyRaider::Flows::Scheduler
|
|
11
|
+
def list_line: (Hash[String, untyped] flow) -> String
|
|
12
|
+
def stop: (RubyRaider::Flows::Error error) -> bot
|
|
13
|
+
end
|
data/sig/ruby_raider.rbs
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
module NextRun
|
|
4
|
+
WDAYS: Array[String]
|
|
5
|
+
SEARCH_DAYS: Integer
|
|
6
|
+
SEARCH_MINUTES: Integer
|
|
7
|
+
|
|
8
|
+
def self?.after: (Schedule schedule, Time now) -> Time
|
|
9
|
+
def self?.next_minute: (Integer minute, Time now) -> Time
|
|
10
|
+
def self?.next_day: (Schedule schedule, Time now) -> Time
|
|
11
|
+
def self?.local: (Date date, Integer hour, Integer minute) -> Time
|
|
12
|
+
def self?.wall: (Time time) -> Array[Integer]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
class Notifier
|
|
4
|
+
MAX: Integer
|
|
5
|
+
TIMEOUT: Integer
|
|
6
|
+
OSASCRIPT: Array[String]
|
|
7
|
+
NOTIFY_SEND: Array[String]
|
|
8
|
+
MARKUP: Hash[String, String]
|
|
9
|
+
TOAST_SCRIPT: String
|
|
10
|
+
POWERSHELL_ARGS: Array[String]
|
|
11
|
+
|
|
12
|
+
@runner: Os::CommandRunner
|
|
13
|
+
@host_os: String
|
|
14
|
+
@err: IO | StringIO
|
|
15
|
+
@warned: bool
|
|
16
|
+
@powershell: String
|
|
17
|
+
|
|
18
|
+
def self.powershell: () -> String
|
|
19
|
+
|
|
20
|
+
def initialize: (?runner: Os::CommandRunner, ?host_os: String, ?err: IO | StringIO, ?powershell: String) -> void
|
|
21
|
+
def notify: (String title, String message) -> bool
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def clean: (untyped text) -> String
|
|
26
|
+
def failed: (String reason) -> false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
module Os
|
|
4
|
+
class CommandRunner
|
|
5
|
+
MISSING: Integer
|
|
6
|
+
TIMED_OUT: Integer
|
|
7
|
+
|
|
8
|
+
class Result
|
|
9
|
+
attr_reader stdout: String
|
|
10
|
+
attr_reader stderr: String
|
|
11
|
+
attr_reader exitstatus: Integer
|
|
12
|
+
|
|
13
|
+
def initialize: (stdout: String, stderr: String, exitstatus: Integer) -> void
|
|
14
|
+
def success?: () -> bool
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call: (*String argv, ?stdin_data: String?, ?env: Hash[String, String], ?timeout: Numeric?) -> Result
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def reader: (IO io) -> Thread
|
|
22
|
+
def feed: (IO stdin, String? data) -> void
|
|
23
|
+
def timed_out: (String command, Numeric? timeout, untyped wait) -> Result
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
module Os
|
|
4
|
+
class Cron
|
|
5
|
+
CRONTAB: String
|
|
6
|
+
TAG: String
|
|
7
|
+
MAX_LINE: Integer
|
|
8
|
+
OS_ID: Regexp
|
|
9
|
+
|
|
10
|
+
@runner: CommandRunner
|
|
11
|
+
|
|
12
|
+
def initialize: (?runner: CommandRunner) -> void
|
|
13
|
+
def label: () -> String
|
|
14
|
+
def missed_runs_note: () -> String
|
|
15
|
+
def line: (Schedule schedule) -> String
|
|
16
|
+
def install: (Schedule schedule) -> true
|
|
17
|
+
def remove: (String os_id) -> bool
|
|
18
|
+
def present?: (String os_id) -> bool
|
|
19
|
+
def usable?: () -> bool
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def fields: (Schedule schedule) -> String
|
|
24
|
+
def weekday: (String day) -> Integer
|
|
25
|
+
def escape: (String word) -> String
|
|
26
|
+
def without: (String table, String os_id) -> [String, bool]
|
|
27
|
+
def our_command?: (String text, String? name) -> bool
|
|
28
|
+
def read: () -> String
|
|
29
|
+
def save: (String table) -> void
|
|
30
|
+
def checked_os_id: (untyped os_id) -> String
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
module Os
|
|
4
|
+
class Launchd
|
|
5
|
+
LAUNCHCTL: String
|
|
6
|
+
PLUTIL: String
|
|
7
|
+
NOFOLLOW: Integer
|
|
8
|
+
ESCAPES: Hash[String, String]
|
|
9
|
+
HEADER: String
|
|
10
|
+
OS_ID: Regexp
|
|
11
|
+
|
|
12
|
+
@runner: CommandRunner
|
|
13
|
+
@home: String
|
|
14
|
+
@uid: Integer
|
|
15
|
+
|
|
16
|
+
def initialize: (?runner: CommandRunner, ?home: String, ?uid: Integer) -> void
|
|
17
|
+
def label: () -> String
|
|
18
|
+
def plist: (Schedule schedule) -> String
|
|
19
|
+
def plist_path: (String os_id) -> String
|
|
20
|
+
def install: (Schedule schedule) -> true
|
|
21
|
+
def remove: (String os_id) -> bool
|
|
22
|
+
def present?: (String os_id) -> bool
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def launchctl: (*String args) -> CommandRunner::Result
|
|
27
|
+
def domain: () -> String
|
|
28
|
+
def service: (String os_id) -> String
|
|
29
|
+
def unlink: (String path) -> bool
|
|
30
|
+
def agents_dir: () -> String
|
|
31
|
+
def checked_os_id: (untyped os_id) -> String
|
|
32
|
+
def escape: (untyped text) -> String
|
|
33
|
+
def string: (untyped text) -> Array[String]
|
|
34
|
+
def integer: (Integer number) -> Array[String]
|
|
35
|
+
def array: (Array[Array[String]] items) -> Array[String]
|
|
36
|
+
def dict: (Array[[String, Array[String]]] pairs) -> Array[String]
|
|
37
|
+
def indent: (Array[String] lines) -> Array[String]
|
|
38
|
+
def calendar: (Schedule schedule) -> Array[String]
|
|
39
|
+
def weekday: (String day) -> Integer
|
|
40
|
+
def discard: (String path) -> String
|
|
41
|
+
def write: (String path, String text) -> void
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
module Os
|
|
4
|
+
class Linux
|
|
5
|
+
@primary: Systemd | Cron
|
|
6
|
+
@other: Cron?
|
|
7
|
+
@systemd: Systemd
|
|
8
|
+
|
|
9
|
+
def self.pick: (?CommandRunner runner) -> Linux
|
|
10
|
+
|
|
11
|
+
def initialize: (?runner: CommandRunner, ?systemd: Systemd, ?cron: Cron) -> void
|
|
12
|
+
def label: () -> String
|
|
13
|
+
def missed_runs_note: () -> String?
|
|
14
|
+
def install: (Schedule schedule) -> true
|
|
15
|
+
def remove: (String os_id) -> bool
|
|
16
|
+
def present?: (String os_id) -> bool
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def stranded?: (String os_id) -> bool
|
|
21
|
+
def refuse_stranded: (String os_id) -> void
|
|
22
|
+
|
|
23
|
+
def other_present?: (String os_id) -> bool
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
module Os
|
|
4
|
+
class Systemd
|
|
5
|
+
SYSTEMCTL: String
|
|
6
|
+
NOFOLLOW: Integer
|
|
7
|
+
QUOTED: Hash[String, String]
|
|
8
|
+
CALENDAR: Hash[String, String]
|
|
9
|
+
OS_ID: Regexp
|
|
10
|
+
UNSAFE_PATH: Regexp
|
|
11
|
+
|
|
12
|
+
@runner: CommandRunner
|
|
13
|
+
@home: String
|
|
14
|
+
@config_home: String?
|
|
15
|
+
|
|
16
|
+
def initialize: (?runner: CommandRunner, ?home: String, ?config_home: String?) -> void
|
|
17
|
+
def label: () -> String
|
|
18
|
+
def missed_runs_note: () -> nil
|
|
19
|
+
def service: (Schedule schedule) -> String
|
|
20
|
+
def timer: (Schedule schedule) -> String
|
|
21
|
+
def unit_path: (String os_id, String kind) -> String
|
|
22
|
+
def install: (Schedule schedule) -> true
|
|
23
|
+
def remove: (String os_id) -> bool
|
|
24
|
+
def present?: (String os_id) -> bool
|
|
25
|
+
def usable?: () -> bool
|
|
26
|
+
def unit_files?: (String os_id) -> bool
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def systemctl: (*String args) -> CommandRunner::Result
|
|
31
|
+
def units_dir: () -> String
|
|
32
|
+
def checked_os_id: (untyped os_id) -> String
|
|
33
|
+
def checked_path: (String path) -> void
|
|
34
|
+
def quote: (String text) -> String
|
|
35
|
+
def specifiers: (String text) -> String
|
|
36
|
+
def unlink: (String path) -> bool
|
|
37
|
+
def discard: (String os_id) -> String
|
|
38
|
+
def write: (Hash[String, String] texts) -> void
|
|
39
|
+
def temp_file: (String path, String text) -> String
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
module Os
|
|
4
|
+
class TaskScheduler
|
|
5
|
+
interface _Clock
|
|
6
|
+
def now: () -> Time
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
FOLDER: String
|
|
10
|
+
ESCAPES: Hash[String, String]
|
|
11
|
+
NAMESPACE: String
|
|
12
|
+
OS_ID: Regexp
|
|
13
|
+
NEEDS_QUOTES: Regexp
|
|
14
|
+
GLOB: Regexp
|
|
15
|
+
EXPANDS: Regexp
|
|
16
|
+
DAY_NAMES: Array[String]
|
|
17
|
+
|
|
18
|
+
@runner: CommandRunner
|
|
19
|
+
@clock: _Clock
|
|
20
|
+
@schtasks: String
|
|
21
|
+
|
|
22
|
+
def self.quote: (String arg) -> String
|
|
23
|
+
def self.glob_quote: (String arg) -> String
|
|
24
|
+
def self.odd_doubled: (String slashes) -> String
|
|
25
|
+
def self.join: (Array[String] args) -> String
|
|
26
|
+
|
|
27
|
+
def initialize: (?runner: CommandRunner, ?clock: _Clock, ?schtasks: String) -> void
|
|
28
|
+
def label: () -> String
|
|
29
|
+
def task_name: (untyped os_id) -> String
|
|
30
|
+
def task_xml: (Schedule schedule) -> String
|
|
31
|
+
def install: (Schedule schedule) -> true
|
|
32
|
+
def remove: (String os_id) -> bool
|
|
33
|
+
def present?: (String os_id) -> bool
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def schtasks: (*String args) -> CommandRunner::Result
|
|
38
|
+
def list: () -> CommandRunner::Result
|
|
39
|
+
def listed?: (CommandRunner::Result listing, String name) -> bool
|
|
40
|
+
def refuse_expansion: (Schedule schedule) -> void
|
|
41
|
+
def checked_os_id: (untyped os_id) -> String
|
|
42
|
+
def escape: (untyped value) -> String
|
|
43
|
+
def text: (String name, untyped value) -> Array[String]
|
|
44
|
+
def element: (String tag, Array[untyped] children) -> Array[String]
|
|
45
|
+
def indent: (Array[String] lines) -> Array[String]
|
|
46
|
+
def trigger: (Schedule schedule) -> Array[untyped]
|
|
47
|
+
def calendar: (Schedule schedule, String every) -> Array[String]
|
|
48
|
+
def settings: () -> Array[Array[String]]
|
|
49
|
+
def action: (Schedule schedule) -> Array[Array[String]]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
NAME: Regexp
|
|
4
|
+
RESERVED: Array[String]
|
|
5
|
+
|
|
6
|
+
def self.valid_name?: (untyped name) -> bool
|
|
7
|
+
def self.printable: (untyped text) -> String
|
|
8
|
+
|
|
9
|
+
class Error < StandardError
|
|
10
|
+
attr_reader exit_code: Integer
|
|
11
|
+
|
|
12
|
+
def initialize: (String message, ?exit_code: Integer) -> void
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Project
|
|
16
|
+
UNSUPPORTED: String
|
|
17
|
+
GITIGNORE_LINES: Array[String]
|
|
18
|
+
TEMPLATES: Hash[String, String]
|
|
19
|
+
PIN: Regexp
|
|
20
|
+
FLOWS_PIN: Array[Integer]
|
|
21
|
+
|
|
22
|
+
attr_reader root: String
|
|
23
|
+
|
|
24
|
+
def initialize: (String path) -> void
|
|
25
|
+
def path: (String relative) -> String
|
|
26
|
+
def flow_file: (String name) -> String
|
|
27
|
+
def flow_names: () -> Array[String]
|
|
28
|
+
def set_up?: () -> bool
|
|
29
|
+
def unsupported_reason: () -> String?
|
|
30
|
+
def init: (?out: IO | StringIO, ?err: IO | StringIO) -> Integer
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def template_dir: () -> String
|
|
35
|
+
def read: (String relative) -> String?
|
|
36
|
+
def selenium?: (String gemfile) -> bool
|
|
37
|
+
def runtime?: () -> bool
|
|
38
|
+
def old_pin?: (String gemfile) -> bool
|
|
39
|
+
def write_templates: () -> Array[String]
|
|
40
|
+
def add_gitignore_lines: () -> Array[String]
|
|
41
|
+
def pin_gem: () -> bool
|
|
42
|
+
def warn_unknown_pin: (IO | StringIO err) -> void
|
|
43
|
+
def report: (IO | StringIO out, Array[String] created, Array[String] updated, bool pinned) -> void
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
class RunLog
|
|
4
|
+
MAX_BYTES: Integer
|
|
5
|
+
NOFOLLOW: Integer
|
|
6
|
+
NONBLOCK: Integer
|
|
7
|
+
SUMMARY: Array[String]
|
|
8
|
+
|
|
9
|
+
def self.summaries: (Project project) -> Array[Hash[String, untyped]]
|
|
10
|
+
|
|
11
|
+
def initialize: (Project project, String name) -> void
|
|
12
|
+
def lines: () -> Array[Hash[String, untyped]]
|
|
13
|
+
def last: () -> Hash[String, untyped]?
|
|
14
|
+
def last_result: () -> Hash[String, untyped]?
|
|
15
|
+
def find: (String run_id) -> Hash[String, untyped]?
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def tail: () -> String
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
class Runner
|
|
4
|
+
EXIT_CODES: Hash[String, Integer]
|
|
5
|
+
INPUT: Regexp
|
|
6
|
+
INPUT_RULE: String
|
|
7
|
+
TRIGGERS: Array[String]
|
|
8
|
+
RETRY_WAIT: Integer
|
|
9
|
+
|
|
10
|
+
def self.spawn: (Hash[String, String?] env, Array[String] argv, String chdir) -> Integer?
|
|
11
|
+
def self.stopped: () -> bool?
|
|
12
|
+
def self.stopped=: (bool) -> bool
|
|
13
|
+
def self.interrupt: (Integer pid) -> void
|
|
14
|
+
|
|
15
|
+
def initialize: (Project project, ?out: IO | StringIO, ?spawner: untyped, ?sleeper: untyped, ?notifier: untyped) -> void
|
|
16
|
+
def run: (String name, ?headed: bool?, ?retries: untyped, ?inputs: Array[String]?, ?trigger: String, ?notify: bool) -> Integer
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def attempt_runs: (String name, Integer attempts, Hash[Symbol, untyped] options) -> String
|
|
21
|
+
def notify_failure: (String name, String status, String trigger) -> bool
|
|
22
|
+
def start_output: (String name, String trigger) -> String
|
|
23
|
+
def stopped: (String name, String status) -> String
|
|
24
|
+
def attempt_run: (String name, Integer attempt, Hash[Symbol, untyped] options) -> String
|
|
25
|
+
def run_env: (String name, Integer attempt, String run_id, Hash[Symbol, untyped] options) -> Hash[String, String?]
|
|
26
|
+
def input_env: (Array[String]? inputs) -> Hash[String, String]
|
|
27
|
+
def checked_trigger: (String trigger) -> String
|
|
28
|
+
def checked_retries: (untyped retries) -> Integer
|
|
29
|
+
def report: (String name, Hash[String, untyped]? line, Integer? code) -> void
|
|
30
|
+
def outcome: (Hash[String, untyped] line) -> String
|
|
31
|
+
def failed_step: (untyped step) -> String
|
|
32
|
+
def duration: (Hash[String, untyped] line) -> (Float | String)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Flows
|
|
3
|
+
class Schedule
|
|
4
|
+
EVERY: Array[String]
|
|
5
|
+
DAYS: Array[String]
|
|
6
|
+
DAYS_RULE: String
|
|
7
|
+
CLOCK: Regexp
|
|
8
|
+
MINUTE: Regexp
|
|
9
|
+
GEM_ENV: Array[String]
|
|
10
|
+
SYSTEM_PATH: Array[String]
|
|
11
|
+
OS_ID_PREFIX: String
|
|
12
|
+
KEYS: Array[String]
|
|
13
|
+
|
|
14
|
+
attr_reader name: String
|
|
15
|
+
attr_reader every: String
|
|
16
|
+
attr_reader at: String
|
|
17
|
+
attr_reader on: String?
|
|
18
|
+
attr_reader retries: Integer
|
|
19
|
+
attr_reader inputs: Array[String]
|
|
20
|
+
attr_reader notify: bool
|
|
21
|
+
attr_reader os_id: String
|
|
22
|
+
attr_reader ruby: String
|
|
23
|
+
attr_reader bundle: String
|
|
24
|
+
attr_reader gem_env: Hash[String, String]
|
|
25
|
+
attr_reader project_root: String
|
|
26
|
+
attr_reader created_at: String
|
|
27
|
+
|
|
28
|
+
def self.build: (project: Project, name: String, every: String, ?at: String?, ?on: String?, ?retries: untyped, ?inputs: Array[String], ?notify: bool, ?now: Time) -> Schedule
|
|
29
|
+
def self.from_h: (untyped hash, String project_root) -> Schedule
|
|
30
|
+
def self.os_id_for: (String project_root, String name) -> String
|
|
31
|
+
def self.bundle_path: () -> String
|
|
32
|
+
def self.captured_gem_env: () -> Hash[String, String]
|
|
33
|
+
|
|
34
|
+
def initialize: (name: untyped, every: untyped, at: untyped, on: untyped, retries: untyped, inputs: untyped, os_id: untyped, ruby: untyped, bundle: untyped, gem_env: untyped, project_root: untyped, created_at: untyped, ?notify: untyped) -> void
|
|
35
|
+
def hour: () -> Integer
|
|
36
|
+
def minute: () -> Integer
|
|
37
|
+
def days: () -> Array[String]
|
|
38
|
+
def argv: () -> Array[String]
|
|
39
|
+
def env: () -> Hash[String, String]
|
|
40
|
+
def working_directory: () -> String
|
|
41
|
+
def log_path: () -> String
|
|
42
|
+
def rule_text: () -> String
|
|
43
|
+
def to_h: () -> Hash[String, untyped]
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def checked_name: (untyped name) -> String
|
|
48
|
+
def checked_every: (untyped every) -> String
|
|
49
|
+
def checked_at: (untyped at) -> String
|
|
50
|
+
def clock: (untyped at) -> String
|
|
51
|
+
def checked_on: (untyped on) -> String?
|
|
52
|
+
def checked_retries: (untyped retries) -> Integer
|
|
53
|
+
def checked_inputs: (untyped inputs) -> Array[String]
|
|
54
|
+
def checked_notify: (untyped notify) -> bool
|
|
55
|
+
def checked_os_id: (untyped os_id) -> String
|
|
56
|
+
def checked_path: (String label, untyped path) -> String
|
|
57
|
+
def checked_gem_env: (untyped gem_env) -> Hash[String, String]
|
|
58
|
+
def checked_text: (String label, untyped text) -> String
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|