1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
|
##
# Copyright (c) 2016 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
"""
Command line usage.
"""
__all__ = [
"Executable",
"Options",
"UsageError",
"exit",
"ExitStatus",
]
import sys
import signal
from os import getpid, kill
from twisted.python.constants import Values, ValueConstant
from twisted.python.usage import Options as TwistedOptions, UsageError
from twisted.python.filepath import FilePath
from twisted.logger import (
globalLogBeginner, textFileLogObserver, jsonFileLogObserver,
FilteringLogObserver, LogLevelFilterPredicate,
LogLevel, InvalidLogLevelError,
Logger,
)
class Executable(object):
"""
Executable.
"""
log = Logger()
@classmethod
def main(cls, argv=sys.argv):
if hasattr(cls, "Options"):
options = cls.Options()
try:
options.parseOptions(argv[1:])
except UsageError as e:
exit(ExitStatus.EX_USAGE, "Error: {}\n\n{}".format(e, options))
else:
options = None
instance = cls(options)
try:
instance.run()
except Exception as e:
exit(ExitStatus.EX_SOFTWARE, "Error: {}\n".format(e))
def __init__(self, options):
self.options = options
def run(self):
self.postOptions()
self.optionallyKill()
self.writePIDFile()
self.startLogging()
self.runReactor()
self.shutDown()
def postOptions(self):
pass
def optionallyKill(self):
pidFile = self.options.get("pidFile")
if self.options.get("kill"):
if pidFile is None:
exit(ExitStatus.EX_USAGE, "--pid-file required to use --kill")
else:
pid = ""
try:
for pid in pidFile.open():
break
except (IOError, OSError):
exit(ExitStatus.EX_DATAERR, "Unable to read pid file.")
try:
pid = int(pid)
except ValueError:
exit(ExitStatus.EX_DATAERR, "Invalid pid file.")
self.startLogging()
self.log.info("Terminating process: {pid}", pid=pid)
kill(pid, signal.SIGTERM)
exit(ExitStatus.EX_OK)
def writePIDFile(self):
pidFile = self.options.get("pidFile")
if pidFile is not None:
pid = getpid()
pidFile.setContent(u"{}\n".format(pid).encode("utf-8"))
def startLogging(self):
logFile = self.options.get("logFile", sys.stderr)
fileLogObserverFactory = self.options.get(
"fileLogObserverFactory", textFileLogObserver
)
fileObserver = fileLogObserverFactory(logFile)
logLevelPredicate = LogLevelFilterPredicate(
defaultLogLevel=self.options.get("logLevel", LogLevel.info)
)
filteringObserver = FilteringLogObserver(
fileObserver, [logLevelPredicate]
)
globalLogBeginner.beginLoggingTo([filteringObserver])
def runReactor(self):
from twisted.internet import reactor
reactor.callWhenRunning(self.whenRunning)
self.log.info("Starting reactor...")
reactor.run()
def whenRunning(self):
pass
def shutDown(self):
pidFile = self.options.get("pidFile")
if pidFile is not None:
pidFile.remove()
class Options(TwistedOptions):
"""
Options.
"""
def opt_version(self):
"""
Print version and exit.
"""
from deps import __version__ as version
exit(ExitStatus.EX_OK, "{}".format(version))
def opt_pid_file(self, name):
"""
File to store process ID in.
"""
self["pidFile"] = FilePath(name)
def opt_kill(self):
"""
Exit running application. (Requires --pid-file.)
"""
self["kill"] = True
def opt_log_file(self, fileName):
"""
Log to file.
("-" for stdout, "+" for stderr. default: "+")
"""
if fileName == "-":
self["logFile"] = sys.stdout
return
if fileName == "+":
self["logFile"] = sys.stderr
return
try:
self["logFile"] = open(fileName, "a")
self.setdefault("fileLogObserverFactory", jsonFileLogObserver)
except EnvironmentError as e:
exit(
ExitStatus.EX_CANTCREAT,
"Unable to open log file {!r}: {}".format(fileName, e)
)
def opt_log_format(self, format):
"""
Set log file format to one of: (text, json).
(default: text for stdout/stderr, otherwise json)
"""
format = format.lower()
if format == "text":
self["fileLogObserverFactory"] = textFileLogObserver
elif format == "json":
self["fileLogObserverFactory"] = jsonFileLogObserver
else:
exit(
ExitStatus.EX_USAGE,
"Invalid log format: {}".format(format)
)
self["logFormat"] = format
def opt_log_level(self, levelName):
"""
Set default log level to one of: {levelNames}.
(default: info)
"""
try:
self["logLevel"] = LogLevel.levelWithName(levelName)
except InvalidLogLevelError:
exit(
ExitStatus.EX_USAGE,
"Invalid log level: {}".format(levelName)
)
# Format the docstring for opt_log_level.
opt_log_level.__doc__ = opt_log_level.__doc__.format(
levelNames=", ".join([l.name for l in LogLevel.iterconstants()])
)
def exit(status, message=None):
"""
Exit the python interpreter.
"""
if message:
if status == ExitStatus.EX_OK:
out = sys.stdout
else:
out = sys.stderr
out.write(message)
out.write("\n")
sys.exit(status.value)
class ExitStatus(Values):
"""
Exit status codes for system programs.
"""
EX__BASE = 64
EX_OK = ValueConstant(0) # No error
EX_USAGE = ValueConstant(EX__BASE) # Command line usage error
EX_DATAERR = ValueConstant(EX__BASE + 1) # Invalid user data
EX_NOINPUT = ValueConstant(EX__BASE + 2) # Can't open input file
EX_NOUSER = ValueConstant(EX__BASE + 3) # Can't look up user
EX_NOHOST = ValueConstant(EX__BASE + 4) # Can't look up host
EX_UNAVAILABLE = ValueConstant(EX__BASE + 5) # Service unavailable
EX_SOFTWARE = ValueConstant(EX__BASE + 6) # Internal software error
EX_OSERR = ValueConstant(EX__BASE + 7) # System error
EX_OSFILE = ValueConstant(EX__BASE + 8) # System file missing
EX_CANTCREAT = ValueConstant(EX__BASE + 9) # Can't create output file
EX_IOERR = ValueConstant(EX__BASE + 10) # I/O error
EX_TEMPFAIL = ValueConstant(EX__BASE + 11) # Temporary failure
EX_PROTOCOL = ValueConstant(EX__BASE + 12) # Remote protocol error
EX_NOPERM = ValueConstant(EX__BASE + 13) # Permission denied
EX_CONFIG = ValueConstant(EX__BASE + 14) # Configuration error
EX__MAX = EX_CONFIG.value
|