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.
 
 
 

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