The little things give you away... A collection of various small helper stuff
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

217 lines
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[@]}"