Add a jobs-summary script on the os-control servers
Signed-off-by: Aurélien Bompard <aurelien@bompard.org>
This commit is contained in:
parent
8924984ac0
commit
91bd11b0cc
2 changed files with 144 additions and 0 deletions
138
files/scripts/jobs-summary
Normal file
138
files/scripts/jobs-summary
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# vim: set et ts=4 sw=4 si
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import datetime
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from enum import Enum
|
||||||
|
from subprocess import run, PIPE
|
||||||
|
|
||||||
|
|
||||||
|
class TermHandler(type):
|
||||||
|
|
||||||
|
def is_tty(cls):
|
||||||
|
if "NO_COLOR" in os.environ:
|
||||||
|
return False
|
||||||
|
if "FORCE_COLOR" in os.environ:
|
||||||
|
return True
|
||||||
|
return sys.stdout.isatty()
|
||||||
|
|
||||||
|
def __getattr__(cls, name):
|
||||||
|
if cls.is_tty():
|
||||||
|
return getattr(cls, f"_{name}")
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
class Color(metaclass=TermHandler):
|
||||||
|
_END = '\033[0m'
|
||||||
|
_HEADER = '\033[95m'
|
||||||
|
_OKBLUE = '\033[94m'
|
||||||
|
_OKCYAN = '\033[96m'
|
||||||
|
_OKGREEN = '\033[92m'
|
||||||
|
_WARNING = '\033[93m'
|
||||||
|
_FAIL = '\033[91m'
|
||||||
|
_BOLD = '\033[1m'
|
||||||
|
_UNDERLINE = '\033[4m'
|
||||||
|
|
||||||
|
# def __getattribute__(self, name):
|
||||||
|
# print("Calling __getattribute__")
|
||||||
|
# return object.__getattribute__(self, f"_{name}")
|
||||||
|
|
||||||
|
|
||||||
|
class Status(Enum):
|
||||||
|
ACTIVE = f"{Color.OKBLUE}ACTIVE{Color.END}"
|
||||||
|
FAILED = f"{Color.FAIL}FAILED{Color.END}"
|
||||||
|
DONE = f"{Color.OKGREEN}DONE{Color.END}"
|
||||||
|
UNKNOWN = f"{Color.WARNING}UNKNOWN{Color.END}"
|
||||||
|
|
||||||
|
|
||||||
|
def fromisoformat(date_string):
|
||||||
|
try:
|
||||||
|
return datetime.datetime.fromisoformat(date_string.rstrip("Z"))
|
||||||
|
except AttributeError:
|
||||||
|
return datetime.datetime(
|
||||||
|
year=int(date_string[0:4]),
|
||||||
|
month=int(date_string[5:7]),
|
||||||
|
day=int(date_string[8:10]),
|
||||||
|
hour=int(date_string[11:13]),
|
||||||
|
minute=int(date_string[14:16]),
|
||||||
|
second=int(date_string[17:19]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_duration(date_string, until=None):
|
||||||
|
until = fromisoformat(until) if until is not None else datetime.datetime.now()
|
||||||
|
date = fromisoformat(date_string)
|
||||||
|
duration = int((until - date).total_seconds())
|
||||||
|
hours = int(duration / 3600)
|
||||||
|
minutes = int((duration % 3600) / 60)
|
||||||
|
seconds = int(duration % 60)
|
||||||
|
output = [
|
||||||
|
f"{hours}h" if hours else "",
|
||||||
|
f"{minutes}m" if minutes else "",
|
||||||
|
f"{seconds}s" if seconds else "",
|
||||||
|
]
|
||||||
|
return "".join(output)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json_output(project_name):
|
||||||
|
jobs = {}
|
||||||
|
statuses = {}
|
||||||
|
result = run(["oc", "-n", project_name, "get", "jobs", "--sort-by=.metadata.creationTimestamp", "-o", "json"], stdout=PIPE, check=True, universal_newlines=True)
|
||||||
|
result = json.loads(result.stdout)
|
||||||
|
for job in result["items"]:
|
||||||
|
cronjob = list([ref["name"] for ref in job["metadata"]["ownerReferences"] if ref["kind"] == "CronJob"])[0]
|
||||||
|
full_name = job["metadata"]["name"]
|
||||||
|
jobs[cronjob] = full_name
|
||||||
|
details = None
|
||||||
|
if job["status"].get("active", 0) > 0:
|
||||||
|
status = Status.ACTIVE
|
||||||
|
duration = get_duration(job['status']['startTime'])
|
||||||
|
details = f"for {duration}"
|
||||||
|
failures = job["status"].get("failed", 0)
|
||||||
|
if failures > 0:
|
||||||
|
details += f" {Color.WARNING}(failed {failures} time{'s' if failures > 1 else ''}, was restarted){Color.END}"
|
||||||
|
elif job["status"].get("failed", 0) > 0:
|
||||||
|
status = Status.FAILED
|
||||||
|
try:
|
||||||
|
completed = job['status']['conditions'][0]['lastTransitionTime']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
at = get_duration(completed)
|
||||||
|
duration = get_duration(job['status']['startTime'], completed)
|
||||||
|
details = f"{at} ago, ran for {duration}"
|
||||||
|
elif job["status"].get("succeeded") == job["spec"]["completions"]:
|
||||||
|
status = Status.DONE
|
||||||
|
completed = job['status']['completionTime']
|
||||||
|
at = get_duration(completed)
|
||||||
|
duration = get_duration(job['status']['startTime'], completed)
|
||||||
|
details = f"{at} ago, ran for {duration}"
|
||||||
|
else:
|
||||||
|
status = Status.UNKNOWN
|
||||||
|
statuses[cronjob] = (status, details)
|
||||||
|
return jobs, statuses
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = ArgumentParser()
|
||||||
|
parser.add_argument("project_name")
|
||||||
|
args = parser.parse_args()
|
||||||
|
jobs, statuses = parse_json_output(args.project_name)
|
||||||
|
for name in sorted(jobs):
|
||||||
|
full_name = jobs[name]
|
||||||
|
status, details = statuses[name]
|
||||||
|
showlogs = f"oc -n {args.project_name} logs job/{full_name}"
|
||||||
|
if status == Status.ACTIVE:
|
||||||
|
showlogs += " -f --since 1s"
|
||||||
|
else:
|
||||||
|
showlogs += " | less"
|
||||||
|
print(f"{Color.BOLD}—→ {name}:{Color.END} {status.value} {details or ''}")
|
||||||
|
print(showlogs)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -42,6 +42,12 @@
|
||||||
name: /root/oc-client.rpm
|
name: /root/oc-client.rpm
|
||||||
state: installed
|
state: installed
|
||||||
|
|
||||||
|
- name: copy the jobs-summary script
|
||||||
|
copy:
|
||||||
|
src: "{{ files }}/scripts/jobs-summary"
|
||||||
|
dest: /usr/local/bin/jobs-summary
|
||||||
|
mode: 0755
|
||||||
|
|
||||||
- import_tasks: "{{ tasks_path }}/yumrepos.yml"
|
- import_tasks: "{{ tasks_path }}/yumrepos.yml"
|
||||||
- import_tasks: "{{ tasks_path }}/motd.yml"
|
- import_tasks: "{{ tasks_path }}/motd.yml"
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue