Module: LaunchAgentManagementInstanceMethods
- Includes:
- LaunchAgentConstants, Which
- Included in:
- LaunchAgentManager
- Defined in:
- lib/kanseishitsu/launch_agent_manager.rb
Overview
Define module LaunchAgentManagementInstanceMethods
Constant Summary
Constants included from LaunchAgentConstants
LaunchAgentConstants::BOOTOUT_TEMPLATE, LaunchAgentConstants::CDATA_PATTERN, LaunchAgentConstants::DEFAULT_ENCODING, LaunchAgentConstants::DOCTYPES, LaunchAgentConstants::DOCTYPE_ELEMENT, LaunchAgentConstants::INTERVALS, LaunchAgentConstants::INTERVAL_XPATH_TEMPLATE, LaunchAgentConstants::KEYS_REQUIRING_CDATA, LaunchAgentConstants::LABEL_NAMESPACE, LaunchAgentConstants::LABEL_XPATH, LaunchAgentConstants::LAUNCHCTL_TEMPLATE, LaunchAgentConstants::LAUNCH_AGENTS_DIR_PATH, LaunchAgentConstants::LOCAL_STRING_XPATH, LaunchAgentConstants::ON_LOGIN, LaunchAgentConstants::PROGRAM_ARGUMENTS_XPATH, LaunchAgentConstants::REMOVE_TEMPLATE, LaunchAgentConstants::SCHEDULE_PARTS_COUNT, LaunchAgentConstants::SCHEDULE_XPATH_TEMPLATE, LaunchAgentConstants::START_CALENDAR_INTERVAL_XPATH, LaunchAgentConstants::SUCCESS_MESSAGE, LaunchAgentConstants::WATCH_PATHS_XPATH
Instance Method Summary collapse
-
#create_launch_agent(exe, exe_path, schedule) ⇒ Object
Create a file monitor launch agent.
-
#define_calendar_interval(cron_schedule) ⇒ Object
Define cron settings for plist.
-
#define_plist_contents(label, args, cron_schedule) ⇒ Object
Define the plist contents rubocop: disable Metrics/MethodLength.
-
#derive_job_label_from_file_path(file_path) ⇒ Object
Create the job name based on the given file path.
- #doctype ⇒ Object
- #execute(command) ⇒ Object
-
#extract_program_path(executable_with_args) ⇒ Object
Extract program path and arguments, verifying executable rubocop: disable Metrics/MethodLength.
-
#generate_plist_xml(definition_hash) ⇒ Object
Use Nokogiri to create a launchd plist XML document.
-
#launch_agent_labels ⇒ Object
Return a list of all user launch agent labels.
-
#list_launch_agent_labels ⇒ Object
List labels of launch agents.
-
#load_launchd_job(plist_path) ⇒ Object
Load the launchd job.
-
#parse_plist(plist_path) ⇒ Object
Function to parse plist and extract crontab-like schedule rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength.
-
#parse_schedule(doc) ⇒ Object
Parse the calendar interval.
-
#remove_launch_agent(label) ⇒ Object
Remove plist Launch Agents by label rubocop: disable Metrics/MethodLength.
-
#save_plist(label, doc) ⇒ Object
Save plist file to LaunchAgents directory; do not overwrite an already existing file with the same name at the generated path.
-
#show_all_launch_agents ⇒ Object
Show the relevant launch agents.
Methods included from Which
#executable?, #explicit_which, #find_executable_in_path, #portable_which
Instance Method Details
#create_launch_agent(exe, exe_path, schedule) ⇒ Object
Create a file monitor launch agent
208 209 210 211 212 213 214 215 216 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 208 def create_launch_agent(exe, exe_path, schedule) args = extract_program_path(exe) label = derive_job_label_from_file_path(exe_path || args.first) definition = define_plist_contents(label, args, schedule) doc = generate_plist_xml(definition) plist_path = save_plist(label, doc) load_launchd_job(plist_path) puts format(SUCCESS_MESSAGE, label: label) end |
#define_calendar_interval(cron_schedule) ⇒ Object
Define cron settings for plist
51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 51 def define_calendar_interval(cron_schedule) cron_fields = cron_schedule.split calendar_interval = {} INTERVALS.each do |key| value = cron_fields.shift raise "Invalid cron string: #{cron_schedule}" if value.nil? || value.empty? calendar_interval[key] = value end calendar_interval end |
#define_plist_contents(label, args, cron_schedule) ⇒ Object
Define the plist contents rubocop: disable Metrics/MethodLength
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 65 def define_plist_contents(label, args, cron_schedule) definition = {} definition['Label'] = label definition['StartCalendarInterval'] = define_calendar_interval(cron_schedule) unless (system_path = ENV.fetch('PATH', nil)).nil? || system_path.empty? definition['EnvironmentVariables'] = {} definition['EnvironmentVariables']['PATH'] = system_path end if args.length == 1 definition['Program'] = args.first else definition['ProgramArguments'] = args end definition end |
#derive_job_label_from_file_path(file_path) ⇒ Object
Create the job name based on the given file path
44 45 46 47 48 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 44 def derive_job_label_from_file_path(file_path) label = LABEL_NAMESPACE.dup label << File.basename(file_path, '.*') label.join('.') end |
#doctype ⇒ Object
104 105 106 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 104 def doctype format(DOCTYPE_ELEMENT, doctypes: DOCTYPES.join(' ')) end |
#execute(command) ⇒ Object
94 95 96 97 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 94 def execute(command) log.debug "Executing command: #{command}" system(command) end |
#extract_program_path(executable_with_args) ⇒ Object
Extract program path and arguments, verifying executable rubocop: disable Metrics/MethodLength
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 25 def extract_program_path(executable_with_args) *args = if executable_with_args.respond_to?(:split) executable_with_args.split else executable_with_args end exe_path = which(args.shift) if exe_path.nil? || !File.exist?(exe_path) abort "Cannot find executable in path: #{exe_path}" end if exe_path.nil? || !File.executable?(exe_path) || File.directory?(exe_path) abort "Given file is not executable: #{exe_path}" end args.unshift(exe_path) args end |
#generate_plist_xml(definition_hash) ⇒ Object
Use Nokogiri to create a launchd plist XML document
109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 109 def generate_plist_xml(definition_hash) doc = Nokogiri::XML(doctype) doc.encoding = DEFAULT_ENCODING plist = doc.create_element('plist', version: '1.0') root = doc.create_element('dict') definition_hash.each do |key, value| create_xml_tag(doc, root, key, value) end plist << root doc << plist doc end |
#launch_agent_labels ⇒ Object
Return a list of all user launch agent labels
125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 125 def launch_agent_labels labels = [] Dir.glob(File.join(LAUNCH_AGENTS_DIR_PATH, '*.plist')).each do |file_path| doc = File.open(file_path) { |file| Nokogiri::XML(file) } label_node = doc.xpath(LABEL_XPATH) labels << label_node.text unless label_node.empty? end labels end |
#list_launch_agent_labels ⇒ Object
List labels of launch agents
184 185 186 187 188 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 184 def list_launch_agent_labels launch_agent_labels.each do |label| puts label end end |
#load_launchd_job(plist_path) ⇒ Object
Load the launchd job
100 101 102 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 100 def load_launchd_job(plist_path) execute(format(LAUNCHCTL_TEMPLATE, uid: Process.uid, plist: plist_path)) end |
#parse_plist(plist_path) ⇒ Object
Function to parse plist and extract crontab-like schedule rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength
155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 155 def parse_plist(plist_path) plist = {} doc = Nokogiri::XML(File.read(plist_path)) label = doc.xpath(LABEL_XPATH).text watch_paths = doc.xpath(WATCH_PATHS_XPATH).xpath(LOCAL_STRING_XPATH).map(&:text) program_args = doc.xpath(PROGRAM_ARGUMENTS_XPATH).xpath(LOCAL_STRING_XPATH).map(&:text) schedule = parse_schedule(doc) plist[:label] = label unless label.nil? plist[:command] = program_args.empty? ? label : program_args.join(' ') plist[:watch_paths] = watch_paths unless watch_paths.nil? plist[:schedule] = schedule unless schedule.nil? plist end |
#parse_schedule(doc) ⇒ Object
Parse the calendar interval
138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 138 def parse_schedule(doc) watch_paths_node = doc.xpath(WATCH_PATHS_XPATH) watch_paths = watch_paths_node.xpath(LOCAL_STRING_XPATH).map(&:text) return watch_paths.first unless watch_paths.empty? intervals = doc.xpath(START_CALENDAR_INTERVAL_XPATH).first return ON_LOGIN unless intervals INTERVALS.map do |interval| xpath = format(INTERVAL_XPATH_TEMPLATE, key: interval) intervals.at_xpath(xpath).text rescue '*' end.join(' ') end |
#remove_launch_agent(label) ⇒ Object
Remove plist Launch Agents by label rubocop: disable Metrics/MethodLength
192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 192 def remove_launch_agent(label) log.debug "Removing launch agent: #{label}" plist_path = File.(File.join(LAUNCH_AGENTS_DIR_PATH, "#{label}.plist")) log.debug "Removing launch agent plist definition file: #{plist_path}" if File.exist?(plist_path) execute(format(BOOTOUT_TEMPLATE, uid: Process.uid, label: label)) execute(format(REMOVE_TEMPLATE, uid: Process.uid, label: label)) FileUtils.rm(plist_path) puts "Removed launch agent: #{label}" else warn "Not found; crontab or launch agent: #{label}" end end |
#save_plist(label, doc) ⇒ Object
Save plist file to LaunchAgents directory; do not overwrite an already existing file with the same name at the generated path
84 85 86 87 88 89 90 91 92 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 84 def save_plist(label, doc) FileUtils.mkdir_p(LAUNCH_AGENTS_DIR_PATH) plist_path = File.join(LAUNCH_AGENTS_DIR_PATH, "#{label}.plist") return plist_path if File.exist?(plist_path) log.debug "Contents of plist xml document:\n#{doc.to_xml}" File.write(plist_path, doc.to_xml) plist_path end |
#show_all_launch_agents ⇒ Object
Show the relevant launch agents
172 173 174 175 176 177 178 179 180 181 |
# File 'lib/kanseishitsu/launch_agent_manager.rb', line 172 def show_all_launch_agents Dir.glob(File.join(LAUNCH_AGENTS_DIR_PATH, '*.plist')).map do |plist_path| job = parse_plist(plist_path) if job[:command].empty? puts "@disabled #{plist_path}" else puts "#{job[:schedule]} #{job[:command]}" end end end |