#!/usr/bin/env python3 import sys import subprocess import tempfile import os # Force temp files into a folder inside your project (fully owned by you) custom_temp_dir = os.path.join(os.getcwd(), "tmp") os.makedirs(custom_temp_dir, exist_ok=True) tempfile.tempdir = custom_temp_dir SRCLIB = "src" JCLLIB = "jcl" MVSHOST = "oldcomputernerd.com" RDRPORT = 3505 MVS_PASSWORD = os.environ.get("MVS_BATCH_PASSWORD") def create_jcl_payload(local_file, dataset_name, member_name): with open(local_file, 'r') as f: sysin = f.readlines() # PDS member: Use IEBUPDTE jcl = f""" //UPLOAD JOB (ACCT),'UPLOAD', // USER=@05054,PASSWORD={MVS_PASSWORD}, // CLASS=A,MSGCLASS=H,NOTIFY=@05054 //COPY EXEC PGM=IEBUPDTE,PARM=NEW //SYSPRINT DD SYSOUT=* //SYSUT1 DD DUMMY //SYSUT2 DD DSN={dataset_name},DISP=MOD,UNIT=SYSDA, // DCB=(RECFM=FB,LRECL=80,BLKSIZE=0) //SYSIN DD * """ # Append control statement, source lines, end, and terminator (no leading space on ./) jcl += f"./ ADD NAME={member_name}\n" for line in sysin: line = line.rstrip('\n')[:80] jcl += line.ljust(80) + "\n" jcl += "./ ENDUP\n" jcl += "/*\n" return jcl def upload_source(local_file, dataset_name, member_name, mvshost=MVSHOST): """Upload source code to MVS PDS member""" # Read the source file # full path will come from the job runner # filepath = os.path.join(SRCLIB, local_file) payload = create_jcl_payload(local_file, dataset_name, member_name) # Write JCL to temporary file and submit via netcat with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.jcl') as tmpfile: tmpfile.write(payload) tmpfile.flush() tmpfile_path = tmpfile.name try: with open(tmpfile_path, 'rb') as f: subprocess.run( ['nc', '-w', '5', mvshost, str(RDRPORT)], input=f.read(), check=True, capture_output=True ) print(f"Uploaded {local_file} to {dataset_name}({member_name})") return 0 except subprocess.CalledProcessError as e: print(f"Upload failed: {e}") print("stderr:", e.stderr.decode(errors='ignore')) return 1 finally: # Clean up outside os.unlink(tmpfile_path) def submit_jcl(job, mvshost="oldcomputernerd.com"): """Submit JCL job from local directory""" subjcl = os.path.join(JCLLIB, f"{job.upper()}.jcl") if not os.path.exists(subjcl): print(f"JCL file {subjcl} not found") return 1 subcmd = f"nc -w 5 {mvshost} {RDRPORT} < {subjcl}" try: subprocess.run(subcmd, shell=True, check=True) print(f"Submitted JCL job: {job}") return 0 except subprocess.CalledProcessError as e: print(f"JCL submission failed: {e}") return 1 if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: mvs_job.py [destination_pds] [mvshost]") print() print("Arguments:") print(" local_source_file - Path to source file (required)") print(" destination_pds - PDS destination as DATASET(MEMBER) (optional)") print(" Default: @05054.C90.SOURCE(basename)") print(" mvshost - MVS host (optional, default: oldcomputernerd.com)") print() print("Examples:") print(" mvs_job.py src/sieve11.c") print(" mvs_job.py src/sieve11.c '@05054.C90.SOURCE(SIEVE11)'") print(" mvs_job.py src/hello.c '@05054.C90.SOURCE(HELLO)' mainframe.example.com") print() print("Notes:") print(" - JCL file is assumed to be jcl/.jcl") print(" - Member name defaults to source filename without extension") sys.exit(1) local_file = sys.argv[1] # Extract base name without extension for defaults basename = os.path.splitext(os.path.basename(local_file))[0].upper() valid_host_source_pds_suffixes = ['C', 'ALG', 'ASM', 'BAS', 'COB', 'PAS', 'PL360'] default_suffix = valid_host_source_pds_suffixes[0] # Parse destination PDS (optional second argument) if len(sys.argv) > 2 and sys.argv[2]: destination = sys.argv[2] # Parse PDS syntax: DATASET(MEMBER) if '(' in destination and destination.endswith(')'): dataset_name = destination[:destination.index('(')] member_name = destination[destination.index('(')+1:-1] else: print(f"Error: Invalid PDS syntax '{destination}'. Use format: DATASET(MEMBER)") sys.exit(1) else: # Default destination dataset_name = f"@05054.SRCLIB.{default_suffix}" member_name = basename # JCL job name defaults to basename job = basename.lower() # Optional host override mvshost = sys.argv[3] if len(sys.argv) > 3 else MVSHOST print(f"Source: {local_file}") print(f"Destination: {dataset_name}({member_name})") print(f"JCL: jcl/{job}.jcl") print(f"Host: {mvshost}") print() # Step 1: Upload source to PDS if upload_source(local_file, dataset_name, member_name, mvshost) != 0: sys.exit(1) # Step 2: Submit JCL job submit_jcl(job, mvshost)