The little things give you away... A collection of various small helper stuff
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

217 lignes
7.1 KiB

  1. #!/bin/bash
  2. columns=("JOBID" "URL" "USER" "PIPENICK" "QUEUED" "STARTED" "LAST ACTIVE") # Duplicated in Python code!
  3. function valid_column {
  4. local candidate="$1"
  5. local column
  6. for column in "${columns[@]}"
  7. do
  8. [[ "${candidate}" == "${column}" ]] && return 0
  9. done
  10. return 1
  11. }
  12. sortcolumns=()
  13. filter=
  14. filtercaseinsensitive=
  15. nocolours=
  16. notable=
  17. dates= # Whether to use full dates for the time columns rather than expired time strings
  18. while [[ $# -gt 0 ]]
  19. do
  20. if [[ "$1" == "--help" || "$1" == "-h" ]]
  21. then
  22. echo "Usage: archivebot-jobs [options]" >&2
  23. echo "Prints a table of current AB jobs" >&2
  24. echo "Options:" >&2
  25. echo " --help, -h Show this message and exit." >&2
  26. echo " --sort [-]COLUMN, -s Sort the table by a column (descending if preceded by '-'). This can be used multiple times to refine the sorting." >&2
  27. echo " --filter FILTER, -f Filter the table for rows where a COLUMN has a certain VALUE. If specified multiple times, only the last value is used." >&2
  28. echo " The FILTER has this format: COLUMN{=|<|>|^|*|$|~}VALUE" >&2
  29. echo " = means the value must be exactly as specified; < and > mean it must be less/greater than the specified; ^ and $ mean it must start/end with the specified; * means it must contain the specified; ~ means it must match the specified regex." >&2
  30. echo " --ifilter FILTER, -i Like --filter, but case-insensitive" >&2
  31. echo " --no-colours, --no-colors Don't colourise the last activity column if it's been a while." >&2
  32. echo " --no-table Raw output without feeding through column(1); columns are separated by tabs." >&2
  33. echo " --dates Print dates instead of elapsed times for queued/started/last active columns." >&2
  34. echo "The COLUMNs are the names of each column, printed in capital letters in the first line of the output." >&2
  35. exit 0
  36. elif [[ "$1" == "--sort" || "$1" == "-s" ]]
  37. then
  38. sortcolumns+=("$2")
  39. shift
  40. elif [[ "$1" == "--filter" || "$1" == "-f" ]]
  41. then
  42. filter="$2"
  43. filtercaseinsensitive=
  44. shift
  45. elif [[ "$1" == "--ifilter" || "$1" == "-i" ]]
  46. then
  47. filter="$2"
  48. filtercaseinsensitive=1
  49. shift
  50. elif [[ "$1" == "--no-colours" || "$1" == "--no-colors" ]]
  51. then
  52. nocolours=1
  53. elif [[ "$1" == "--no-table" ]]
  54. then
  55. notable=1
  56. elif [[ "$1" == "--dates" ]]
  57. then
  58. dates=1
  59. else
  60. echo "Unknown option: $1" >&2
  61. exit 1
  62. fi
  63. shift
  64. done
  65. # Validate sortcolumns and filter
  66. if [[ "${filter}" ]]
  67. then
  68. if [[ "${filter}" == *$'\n'* ]]
  69. then
  70. echo "Invalid filter: newlines not allowed" >&2
  71. exit 1
  72. fi
  73. if [[ ! ( "${filter}" == *'='* || "${filter}" == *'<'* || "${filter}" == *'>'* || "${filter}" == *'^'* || "${filter}" == *'*'* || "${filter}" == *'$'* || "${filter}" == *'~'* ) ]]
  74. then
  75. echo "Invalid filter: ${filter}" >&2
  76. exit 1
  77. fi
  78. column="${filter%%[=<>^*$~]*}"
  79. if ! valid_column "${column^^}"
  80. then
  81. echo "Invalid filter column: ${column}" >&2
  82. exit 1
  83. fi
  84. fi
  85. if [[ ${#sortcolumns[@]} -gt 0 ]]
  86. then
  87. for column in "${sortcolumns[@]}"
  88. do
  89. column="${column#-}"
  90. if ! valid_column "${column^^}"
  91. then
  92. echo "Invalid sort column: ${column}" >&2
  93. exit 1
  94. fi
  95. done
  96. else
  97. # Default sort order
  98. sortcolumns+=("JOBID")
  99. fi
  100. if [[ "${notable}" ]]
  101. then
  102. column=("cat")
  103. else
  104. column=("column" "-t" $'-s\t')
  105. fi
  106. jobdata="$(curl -s -H "Accept: application/json" "http://dashboard.at.ninjawedding.org/logs/recent?count=1" 2>/dev/null)"
  107. pipelinedata="$(curl -s -H "Accept: application/json" "http://dashboard.at.ninjawedding.org/pipelines" 2>/dev/null)"
  108. if [[ -z "${jobdata}" || -z "${pipelinedata}" ]]
  109. then
  110. echo "Error retrieving job or pipeline data" >&2
  111. exit 1
  112. fi
  113. { echo "${jobdata}"; echo "${pipelinedata}"; echo "${filter}"; } | python3 -c \
  114. '
  115. if True: # For sensible indentation
  116. import datetime
  117. import json
  118. import sys
  119. import time
  120. currentTime = time.time()
  121. def render_date(ts, coloured = False):
  122. global currentTime
  123. diff = currentTime - ts
  124. colourStr = f"\x1b[{0 if diff < 6 * 3600 else 7};31m" if coloured and diff >= 300 else ""
  125. colourEndStr = "\x1b[0m" if colourStr else ""
  126. if "'${dates}'":
  127. return colourStr + datetime.datetime.fromtimestamp(ts).isoformat(sep = " ") + colourEndStr
  128. if diff <= 0:
  129. return "now"
  130. elif diff < 60:
  131. return "<1 min ago"
  132. elif diff < 86400:
  133. return colourStr + (f"{diff // 3600:.0f}h " if diff >= 3600 else "") + f"{(diff % 3600) // 60:.0f}mn ago" + colourEndStr
  134. else:
  135. return colourStr + f"{diff // 86400:.0f}d {(diff % 86400) // 3600:.0f}h ago" + colourEndStr
  136. jobdata = json.loads(sys.stdin.readline())
  137. pipelinedata = json.loads(sys.stdin.readline())
  138. filter = sys.stdin.readline().strip()
  139. pipelines = {p["id"]: p["nickname"] for p in pipelinedata["pipelines"]}
  140. columns = ("JOBID", "URL", "USER", "PIPENICK", "QUEUED", "STARTED", "LAST ACTIVE") # Duplicated in Bash code!
  141. jobs = []
  142. for j in jobdata:
  143. jobs.append([
  144. j["job_data"]["ident"],
  145. j["job_data"]["url"],
  146. j["job_data"]["started_by"],
  147. pipelines[j["job_data"]["pipeline_id"]] if j["job_data"]["pipeline_id"] in pipelines else "unknown",
  148. j["job_data"]["queued_at"],
  149. j["job_data"]["started_at"],
  150. int(j["ts"]),
  151. ])
  152. # Filter
  153. if filter:
  154. import re
  155. match = re.match(r"^(?P<column>[A-Za-z ]+)(?P<op>[=<>^*$~])(?P<value>.*)$", filter)
  156. filterDict = match.groupdict()
  157. filterDict["column"] = filterDict["column"].upper()
  158. assert filterDict["column"] in columns
  159. columnIdx = columns.index(filterDict["column"])
  160. compFunc = {
  161. "=": lambda a, b: a == b,
  162. "<": lambda a, b: a < b,
  163. ">": lambda a, b: a > b,
  164. "^": lambda a, b: a.startswith(b),
  165. "*": lambda a, b: b in a,
  166. "$": lambda a, b: a.endswith(b),
  167. "~": lambda a, b: re.search(b, a) is not None,
  168. }[filterDict["op"]]
  169. if isinstance(jobs[0][columnIdx], (int, float)):
  170. filterDict["value"] = float(filterDict["value"])
  171. transform = lambda x: x.lower() if "'${filtercaseinsensitive}'" and isinstance(x, str) else x
  172. jobs = [job for job in jobs if compFunc(transform(job[columnIdx]), transform(filterDict["value"]))]
  173. if not jobs:
  174. sys.exit(0)
  175. # Sort
  176. class reversor: # https://stackoverflow.com/a/56842689
  177. def __init__(self, obj):
  178. self.obj = obj
  179. def __eq__(self, other):
  180. return other.obj == self.obj
  181. def __lt__(self, other):
  182. return other.obj < self.obj
  183. sortColumnsRaw = ('"$(printf "'%s', " "${sortcolumns[@]}")"')
  184. sortColumns = tuple(column[1:] if column.startswith("-") else column for column in sortColumnsRaw)
  185. sortColumnAsc = tuple(not column.startswith("-") for column in sortColumnsRaw)
  186. assert all(column.upper() in columns for column in sortColumns)
  187. sortColumnIdxs = tuple(columns.index(column.upper()) for column in sortColumns)
  188. if not "'${dates}'":
  189. sortColumnAsc = tuple(not columnAsc if 4 <= columnIdx <= 6 else columnAsc for columnAsc, columnIdx in zip(sortColumnAsc, sortColumnIdxs))
  190. jobs = sorted(jobs, key = lambda job: tuple(job[columnIdx] if columnAsc else reversor(job[columnIdx]) for column, columnAsc, columnIdx in zip(sortColumns, sortColumnAsc, sortColumnIdxs)))
  191. # Print
  192. print("\t".join(columns))
  193. for job in jobs:
  194. job[4] = render_date(job[4])
  195. job[5] = render_date(job[5])
  196. job[6] = render_date(job[6], coloured = not "'${nocolours}'")
  197. print("\t".join(job))
  198. ' | "${column[@]}"