Add jira_log_work_bulk.py
This commit is contained in:
parent
fb62d21fe7
commit
d2a205a629
315
jira_log_work_bulk.py
Normal file
315
jira_log_work_bulk.py
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
'''
|
||||
Script to log work to a jira ticket.
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright © 2025 Peter Juerss (peter.juerss@gc-gruppe.de)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import shutil
|
||||
import datetime
|
||||
import argparse as ap
|
||||
from getpass import getpass
|
||||
from jira.client import JIRA
|
||||
|
||||
SCRIPT_VERSION = '1.0.0'
|
||||
|
||||
# --- Example
|
||||
'''
|
||||
'''
|
||||
|
||||
# --- Classes - DO NOT CHANGE
|
||||
class ReturnCodes:
|
||||
'''
|
||||
Helper class for return codes
|
||||
'''
|
||||
rcOk = 0
|
||||
rcNok = 1
|
||||
rcUsage = 2
|
||||
rcMissingInput = 3
|
||||
rcMissingOutput = 4
|
||||
rcErrorWriteFile = 5
|
||||
rcErrorReadFile = 6
|
||||
|
||||
class ScriptLogger:
|
||||
'''
|
||||
Simple logging class for unified messaging throughout the script
|
||||
|
||||
It logs to stdout.
|
||||
|
||||
If initialized with a filename (log_file), the log output will be written to that file.
|
||||
|
||||
Default is to keep a history of 5 versions of the log file plus the actual log
|
||||
|
||||
Variables:
|
||||
|
||||
log_file = Name of the log file
|
||||
|
||||
log_dir = Path to the directory to store the log files
|
||||
|
||||
num_logs = number of old log files to keep
|
||||
|
||||
Example:
|
||||
|
||||
from lib.log import ScriptLogger as SL
|
||||
logDir = os.path.abspath('log')
|
||||
lg = SL(log_file='mico.log', log_dir=logDir)
|
||||
lg.log(0, 'Success')
|
||||
'''
|
||||
def __init__(self, log_file=False, log_dir='log', num_logs=5, log_level=1):
|
||||
self.keep_file = ['.gitkeep']
|
||||
self.num_logs = int(num_logs)
|
||||
# self.log_dir = os.path.abspath(log_dir)
|
||||
self.log_dir = log_dir
|
||||
self.log_file = log_file
|
||||
self.log_level = int(log_level)
|
||||
if not self.log_file is False:
|
||||
self.log_file = os.path.join(self.log_dir, self.log_file)
|
||||
self.initLogDir()
|
||||
self.backupLogs()
|
||||
self.initLogFile()
|
||||
|
||||
def log(self, level=int(0), key=str(''), value=str('')):
|
||||
log_level = {
|
||||
'0': '\033[1m\033[92m[I]\033[0m',
|
||||
'1': '\033[1m\033[91m[E]\033[0m',
|
||||
'2': '\033[1m\033[93m[W]\033[0m',
|
||||
'3': '\033[1m\033[94m[T]\033[0m',
|
||||
'4': '\033[1m\033[95m[D]\033[0m',
|
||||
'99': '-'
|
||||
}
|
||||
if value:
|
||||
key = key + ':'
|
||||
msg = '{0:4} {1:36} {2}'.format(log_level[str(level)], key, value)
|
||||
if level == 99:
|
||||
msg_len = 80 - len(key) - 2
|
||||
msg = log_level['99'] * 3 + ' ' + key + ' ' + log_level['99'] * msg_len
|
||||
if self.log_level > 0:
|
||||
self.log_console(msg)
|
||||
if self.log_file:
|
||||
msg = msg.replace('\033[0m', '').replace('\033[1m\033[9', '')[2:]
|
||||
self.log_to_file(msg)
|
||||
|
||||
def log_console(self, data):
|
||||
print(data)
|
||||
|
||||
def log_to_file(self, data):
|
||||
try:
|
||||
with open(self.log_file, 'a') as lf:
|
||||
lf.write(f'{str(datetime.datetime.now())} {data} \n')
|
||||
except Exception as e:
|
||||
self.log_console(2, "Cannot access log file")
|
||||
self.log_console(2, " Function", 'ScriptLogger - log_to_file')
|
||||
self.log_console(2, ' Exception:', str(e))
|
||||
sys.exit(1)
|
||||
|
||||
def initLogDir(self):
|
||||
if not os.path.exists(self.log_dir):
|
||||
try:
|
||||
os.makedirs(self.log_dir)
|
||||
except Exception as myError:
|
||||
self.log_console(2, "Cannot create log directory")
|
||||
self.log_console(2, " Function", 'ScriptLogger - initLogDir')
|
||||
self.log_console(2, ' Exception:', str(myError))
|
||||
sys.exit(1)
|
||||
|
||||
def initLogFile(self):
|
||||
if not os.path.isfile(self.log_file):
|
||||
with open(self.log_file, 'w'):
|
||||
os.utime(self.log_file)
|
||||
|
||||
def backupLogs(self):
|
||||
if os.path.isfile(self.log_file):
|
||||
mod_time = datetime.datetime.fromtimestamp(os.path.getmtime(self.log_file)).strftime("%Y-%m-%d-%H%M%S")
|
||||
shutil.move(self.log_file, self.log_file + '_' + str(mod_time))
|
||||
for filename in sorted(os.listdir(self.log_dir))[:-self.num_logs]:
|
||||
if filename not in self.keep_file:
|
||||
os.remove(os.path.join(os.path.dirname(self.log_file), filename))
|
||||
# --- Classes End
|
||||
|
||||
# --- Functions for the script - DO NOT CHANGE
|
||||
|
||||
def cliBaseParseCli():
|
||||
'''
|
||||
Parsing command line arguments. This is the generic function, copy / duplicate as appropriate
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
|
||||
Returns
|
||||
-------
|
||||
cliArgs.logLevel : int
|
||||
logging level, defaults to 0
|
||||
'''
|
||||
parser = ap.ArgumentParser()
|
||||
### some examples
|
||||
parser.add_argument('-i',
|
||||
'--input',
|
||||
dest='inputFile',
|
||||
nargs=1,
|
||||
type=str,
|
||||
help='Input file in JSON format',
|
||||
required='True')
|
||||
cliArgs = parser.parse_args()
|
||||
return (utilBaseVerifySingleValue(cliArgs.inputFile))
|
||||
|
||||
def utilBaseVerifySingleValue(value):
|
||||
'''
|
||||
Test if value is a list.
|
||||
|
||||
If so, return first element, otherwise return the value as is
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : list, int, float, str
|
||||
|
||||
Returns
|
||||
-------
|
||||
value : int, float, str
|
||||
'''
|
||||
return value[0] if isinstance(value, list) else value
|
||||
|
||||
def utilBaseVerifyBoolean(value):
|
||||
'''
|
||||
Checks a value against a list with true values and returns True if it is in the list.
|
||||
Otherwise it returns false
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : value to verify
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : bool
|
||||
True or False
|
||||
'''
|
||||
trueList = ['true', '0', 'yes']
|
||||
return True if str(value).lower() in trueList else False
|
||||
|
||||
def utilBaseTimeStamp():
|
||||
'''
|
||||
Returns time in seconds since epoch
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : float
|
||||
'''
|
||||
return time.time()
|
||||
|
||||
def getUserPass(username):
|
||||
return getpass(f'Password for user {username}: ')
|
||||
|
||||
def loadJson(sl, inputFile):
|
||||
'''
|
||||
Returns a python dictionary after loading JSON file
|
||||
|
||||
Parameters
|
||||
----------
|
||||
inputFile : str
|
||||
Input file in json format to load
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : dict
|
||||
'''
|
||||
try:
|
||||
with open(inputFile, "r") as f:
|
||||
data = json.load(f)
|
||||
sl.log(0, f"Loaded {inputFile}", "Success")
|
||||
return data
|
||||
except Exception as e:
|
||||
sl.log(1, f"Cannot load file {inputFile}", str(e))
|
||||
sys.exit(ReturnCodes.rcErrorReadFile)
|
||||
|
||||
def connectJira(sl, jiraServer, jiraUser, jiraPassword):
|
||||
'''
|
||||
Connect to JIRA and return the object. Exit with return code != 0 on error
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sl : object
|
||||
|
||||
jiraServer : name of jira server incl. protocol - e.g. https://myjira.mydom.net
|
||||
|
||||
jiraUser : username to connect
|
||||
|
||||
jiraPassword : password of the jira user
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : bool
|
||||
True if successfull, error
|
||||
'''
|
||||
try:
|
||||
jiraOptions = {'server': jiraServer, 'headers': {'Authorization': f'Bearer {jiraPassword}'}}
|
||||
jira = JIRA(options=jiraOptions)
|
||||
sl.log(0, f"Connecting to JIRA {jiraServer}", "Success")
|
||||
return jira
|
||||
except Exception as e:
|
||||
sl.log(1, "Failed to connect to JIRA", str(e))
|
||||
sys.exit(ReturnCodes.rcNok)
|
||||
|
||||
def addWorkLogJira(sl, jc, ticketId, workLog, workLogComment):
|
||||
'''
|
||||
Add worklog to the JIRA ticket. Exit with return code != 0 on error
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sl : object
|
||||
|
||||
jc : Jira object
|
||||
|
||||
ticketId : JIRA Ticket number
|
||||
|
||||
workLog : Time to log to ticket
|
||||
|
||||
workLogComment :
|
||||
|
||||
Returns
|
||||
-------
|
||||
jira : object
|
||||
'''
|
||||
try:
|
||||
jc.add_worklog(ticketId, timeSpent=workLog, comment=workLogComment)
|
||||
sl.log(0, f"Added worklog {workLog} to {ticketId}", "Success")
|
||||
return True
|
||||
except Exception as e:
|
||||
sl.log(1, f"Failed to add worklog to {ticketId}", str(e))
|
||||
sys.exit(ReturnCodes.rcNok)
|
||||
|
||||
def main():
|
||||
t0 = utilBaseTimeStamp()
|
||||
sl = ScriptLogger()
|
||||
inputFile = cliBaseParseCli()
|
||||
data = loadJson(sl, inputFile)
|
||||
if len(data['tickets']) > 0:
|
||||
jiraServer = data['server']
|
||||
jiraUser = data['username']
|
||||
jc = connectJira(sl, jiraServer, jiraUser, getUserPass(jiraUser))
|
||||
for entry in data['tickets']:
|
||||
ticketId = entry['ticketId']
|
||||
workLog = entry['workLog']
|
||||
workLogComment = entry['workLog']
|
||||
addWorkLogJira(sl, jc, ticketId, workLog, workLogComment)
|
||||
else:
|
||||
sl.log(0, "No tickets to work with")
|
||||
sl.log(0, "Time spent (s)", utilBaseTimeStamp()-t0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Reference in a new issue