1
0
mirror of https://github.com/moshix/mvs.git synced 2026-01-11 23:43:00 +00:00
moshix.mvs/SUBMIT.py

405 lines
12 KiB
Python

#!/usr/local/bin/python3
# Copyright © 2018 by John Murray
# No rights reserved.
from ftplib import FTP
import argparse
import multiprocessing
import os
import re
import subprocess
import sys
import time
envEditor = ''
def opts():
parser = argparse.ArgumentParser(description='Submit a job to Z/OS, wait for it to end and get the output back.',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='Notes.\nUserid and password will be obtained from environment variables ZOSUSER and ZOSPSWD if defined.\n'
'Hostname will be obtained from environment variables ZOSHOST if defined.\n'
'A directory called "listing" will be created if one does not exist. The script uses this directory for temporary files and listings.\n'
'If the user decides to edit the listing, the name of the editor will be obtained from environment variable ZOSEDITOR. If the variable is not defined, the editor will default to "x"')
parser.add_argument('-d',
action='store_true',
dest='debug',
default=False,
help='Run the command in debug mode')
parser.add_argument('-e',
action='store_true',
dest='edit',
default=False,
help='Edit the listing file')
parser.add_argument('-w',
default=5,
action='store',
dest='wait',
metavar='seconds',
help='Seconds to sleep before checking for job completion',
type=int)
parser.add_argument('-u',
action='store',
dest='user',
metavar='userid',
help='Userid to sign-in to zOS with')
parser.add_argument('-p',
action='store',
dest='pswd',
metavar='password',
help='Password')
parser.add_argument('-s',
action='store',
dest='host',
metavar='hostname',
help='Hostname of host to submit job to')
parser.add_argument('JCLFile',
action='store',
help='JCL file to submit')
args = parser.parse_args()
return args
def settleEnvs(args):
global envEditor
rc = True
if args.user == None:
user = os.getenv('ZOSUSER')
if user != None and len(user) != 0:
args.user = user
else:
print('User not sepcified and ZOSUSER not defined')
rc = False
if args.pswd == None:
pswd = os.getenv('ZOSPSWD')
if pswd != None and len(pswd) != 0:
args.pswd = pswd
else:
print('Password not sepcified and ZOSPSWD not defined')
rc = False
if args.host == None:
host = os.getenv('ZOSHOST')
if host != None and len(host) != 0:
args.host = host
else:
print('Host not sepcified and ZOSHOST not defined')
rc = False
if args.edit:
envEditor = os.getenv('ZOSEDITOR')
if envEditor == None or len(envEditor) == 0:
envEditor = 'x'
return rc
def debugOpts(args):
if args.debug:
print('Options:')
print('\tedit ' + str(args.edit))
print('\twait ' + str(args.wait))
print('\tuser ' + str(args.user))
print('\tpswd ' + str(args.pswd))
print('\thost ' + str(args.host))
print('\tJCLFile ' + args.JCLFile)
print()
class Error(Exception):
pass
class SubmitError(Error):
def __init__(self,
expression,
message):
self.expression = expression
self.message = message
class Submit:
def __init__(self,
debug=False,
edit=False,
wait=5,
user=None,
pswd=None,
host=None,
editor=None,
JCLFile=None):
self.debug = debug
self.edit = edit
self.wait = wait
self.user = user
self.pswd = pswd
self.host = host
self.editor = editor
self.JCLFile = JCLFile
self.jesLevelRe = re.compile(r'JESINTERFACELEVEL is ([0-9])')
self.jobNameRe = re.compile(r'\/\/([a-zA-Z0-9$#@]+)\s+JOB')
self.jobIDRe = re.compile(r'It is known to JES as ([a-zA-Z0-9$#@]+)')
self.userRe = re.compile(r'\?user\?')
self.pswdRe = re.compile(r'\?pswd\?')
self.includeRe = re.compile(r'\?([^\?]+)\?')
self.endRe = re.compile(r'^\/\/ *$')
self.jesStatus1Re = re.compile(r'^\s*(\S+)\s+(\S+)\s+(\S+)\s*')
self.jesStatus2Re = re.compile(r'^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+')
def processJob(self):
if not os.path.exists('listings'):
os.mkdir('listings')
self.openFTP()
self.jesLevel()
tmpFileName = self.buildJob()
jobID = self.sendFTP(tmpFileName)
listing = self.getJobListing(jobID)
listingFileName = self.saveListing(jobID,
listing)
if self.edit:
self.editListing(listingFileName)
self.closeFTP(jobID)
self.cleanup(tmpFileName)
def openFTP(self):
self.ftp = FTP(host=self.host,
user=self.user,
passwd=self.pswd)
self.ftp.sendcmd('site filetype=jes')
def jesLevel(self):
stat = self.ftp.sendcmd('stat')
jesLevelMatch = self.jesLevelRe.search(stat)
if jesLevelMatch == None:
raise SubmitError('jesLevelInterface',
'JESINTERFACELEVEL not found via stat command')
self.jesLevelInterface = jesLevelMatch.group(1)
if self.debug:
print('jesLevel: ' + self.jesLevelInterface)
def buildJob(self):
try:
jobNameFound = False
jclCards = self.getFile(self.JCLFile)
jclCards = self.fixJobName(jclCards)
jclCards = self.repUserPswd(jclCards)
jclCards = self.includes(jclCards)
tmpFileName = self.createJCLFile(jclCards)
if self.debug:
print('buildJob: ' + tmpFileName)
except OSError as osError:
raise SubmitError('buildJob',
'Problem opening JCLFile ' + self.JCLFile + '(' + str(osError) + ')')
return tmpFileName
def getFile(self,
fileName):
file = open(fileName, 'r')
lines = file.readlines()
file.close()
lines = ''.join(lines)
return lines
def fixJobName(self,
jclCards):
jobNameMatch = self.jobNameRe.search(jclCards)
if jobNameMatch != None:
if self.jesLevelInterface == '1':
self.jobName = self.user + '0'
else:
self.jobName = jobNameMatch.group(1)
jclCards = jclCards.replace(jobNameMatch.group(1),
self.jobName,
1)
if self.debug:
print('fixJobName:' + self.jobName)
else:
raise SubmitError('buildJob',
'Job card not found in ' + self.JCLFile)
return jclCards
def repUserPswd(self,
jclCards):
jclCards = jclCards.replace('?user?',
self.user,
1)
jclCards = jclCards.replace('?pswd?',
self.pswd,
1)
return jclCards
def includes(self,
jclCards):
while True:
includeMatch = self.includeRe.search(jclCards)
if includeMatch != None:
includeCards = self.getFile(includeMatch.group(1))
jclCards = jclCards.replace(includeMatch.group(0) + "\n",
includeCards,
1)
else:
break
return jclCards
def createJCLFile(self,
jclCards):
tmpFileName = 'listings/' + os.path.basename(self.JCLFile) + '.' + str(multiprocessing.current_process().pid)
tmpFile = open(tmpFileName, 'w')
for jclCard in jclCards.split(sep='\n'):
tmpFile.write(jclCard + '\n')
if re.search(r'^\/\/ *$', jclCard) != None:
break
tmpFile.close()
return tmpFileName
def sendFTP(self,
tmpFileName):
zosMessage = self.ftp.storlines('stor ' + os.path.basename(tmpFileName), open(tmpFileName, 'rb'))
return self.getJobID(zosMessage)
def getJobID(self,
zosMessage):
jobIDMatch = self.jobIDRe.search(zosMessage)
if jobIDMatch == None:
raise SubmitError('getJobID',
'Job ID not found in: ' + zosMessage)
jobID = jobIDMatch.group(1)
print('Job ' + self.JCLFile + ' submitted for execution on ' + self.host + ' as ' + jobID)
return jobID
def getJobListing(self,
jobID):
notFound = True
heldOutList = []
listingList = []
while notFound:
time.sleep(int(self.wait))
# These are closures: it has access to heldOutList which is in the current scope
#
def ftpDir(line):
heldOutList.append(line)
def ftpRetr(line):
listingList.append(line)
self.ftp.dir(ftpDir)
for heldOut in heldOutList:
if self.jesLevelInterface == '1':
jesStatus1Match = self.jesStatus1Re.match(heldOut)
job, jobid, state = jesStatus1Match.group(1, 2, 3)
else:
jesStatus2Match = self.jesStatus2Re.match(heldOut)
job, jobid, owner, state = jesStatus2Match.group(1, 2, 3, 4)
if jobid == jobID:
if state == 'OUTPUT':
self.ftp.retrlines('RETR ' + jobid, ftpRetr)
listing = '\n'.join(listingList)
notFound = False
break
return listing
def saveListing(self,
jobID,
listing):
listingFileName = 'listings/' + os.path.basename(self.JCLFile) + '.' + jobID
listingFile = open(listingFileName, 'w')
listingFile.write(listing.encode(encoding="ascii", errors="replace").decode())
listingFile.close()
print('Job output for ' + self.JCLFile + ' is in ' + listingFileName)
return listingFileName
def editListing(self,
listingFileName):
completed = subprocess.run([self.editor, listingFileName])
def closeFTP(self,
jobID):
self.ftp.delete(jobID)
self.ftp.close()
def cleanup(self,
tmpFileName):
completed = subprocess.run(['rm', tmpFileName])
def main():
try:
args = opts()
if settleEnvs(args):
debugOpts(args)
submit = Submit(debug=args.debug,
edit=args.edit,
wait=args.wait,
user=args.user,
pswd=args.pswd,
host=args.host,
editor=envEditor,
JCLFile=args.JCLFile)
submit.processJob()
except SubmitError as submitError:
print('SubmitError: ' + submitError.expression + ' - ' + submitError.message)
if __name__ == '__main__':
main()