1"""
2Functions for creating and working with job IDs
3"""
4
5import datetime
6import hashlib
7import os
8from calendar import month_abbr as months
9
10import salt.utils.stringutils
11
12LAST_JID_DATETIME = None
13
14
15def _utc_now():
16    """
17    Helper method so tests do not have to patch the built-in method.
18    """
19    return datetime.datetime.utcnow()
20
21
22def gen_jid(opts):
23    """
24    Generate a jid
25    """
26    global LAST_JID_DATETIME  # pylint: disable=global-statement
27
28    jid_dt = _utc_now()
29    if not opts.get("unique_jid", False):
30        return "{:%Y%m%d%H%M%S%f}".format(jid_dt)
31    if LAST_JID_DATETIME and LAST_JID_DATETIME >= jid_dt:
32        jid_dt = LAST_JID_DATETIME + datetime.timedelta(microseconds=1)
33    LAST_JID_DATETIME = jid_dt
34    return "{:%Y%m%d%H%M%S%f}_{}".format(jid_dt, os.getpid())
35
36
37def is_jid(jid):
38    """
39    Returns True if the passed in value is a job id
40    """
41    if not isinstance(jid, str):
42        return False
43    if len(jid) != 20 and (len(jid) <= 21 or jid[20] != "_"):
44        return False
45    try:
46        int(jid[:20])
47        return True
48    except ValueError:
49        return False
50
51
52def jid_to_time(jid):
53    """
54    Convert a salt job id into the time when the job was invoked
55    """
56    jid = str(jid)
57    if len(jid) != 20 and (len(jid) <= 21 or jid[20] != "_"):
58        return ""
59    year = jid[:4]
60    month = jid[4:6]
61    day = jid[6:8]
62    hour = jid[8:10]
63    minute = jid[10:12]
64    second = jid[12:14]
65    micro = jid[14:20]
66
67    ret = "{}, {} {} {}:{}:{}.{}".format(
68        year, months[int(month)], day, hour, minute, second, micro
69    )
70    return ret
71
72
73def format_job_instance(job):
74    """
75    Format the job instance correctly
76    """
77    ret = {
78        "Function": job.get("fun", "unknown-function"),
79        "Arguments": list(job.get("arg", [])),
80        # unlikely but safeguard from invalid returns
81        "Target": job.get("tgt", "unknown-target"),
82        "Target-type": job.get("tgt_type", "list"),
83        "User": job.get("user", "root"),
84    }
85
86    if "metadata" in job:
87        ret["Metadata"] = job.get("metadata", {})
88    else:
89        if "kwargs" in job:
90            if "metadata" in job["kwargs"]:
91                ret["Metadata"] = job["kwargs"].get("metadata", {})
92    return ret
93
94
95def format_jid_instance(jid, job):
96    """
97    Format the jid correctly
98    """
99    ret = format_job_instance(job)
100    ret.update({"StartTime": jid_to_time(jid)})
101    return ret
102
103
104def format_jid_instance_ext(jid, job):
105    """
106    Format the jid correctly with jid included
107    """
108    ret = format_job_instance(job)
109    ret.update({"JID": jid, "StartTime": jid_to_time(jid)})
110    return ret
111
112
113def jid_dir(jid, job_dir=None, hash_type="sha256"):
114    """
115    Return the jid_dir for the given job id
116    """
117    if not isinstance(jid, str):
118        jid = str(jid)
119    jhash = getattr(hashlib, hash_type)(
120        salt.utils.stringutils.to_bytes(jid)
121    ).hexdigest()
122
123    parts = []
124    if job_dir is not None:
125        parts.append(job_dir)
126    parts.extend([jhash[:2], jhash[2:]])
127    return os.path.join(*parts)
128