initial attempt at a deletion tool
This commit is contained in:
parent
2d657c0809
commit
12255afeea
@ -31,7 +31,7 @@ jobs:
|
|||||||
nc -h
|
nc -h
|
||||||
echo "=== Debug: Setup complete ==="
|
echo "=== Debug: Setup complete ==="
|
||||||
|
|
||||||
- name: Find changed source files
|
- name: Find changed and deleted source files
|
||||||
id: files
|
id: files
|
||||||
run: |
|
run: |
|
||||||
echo "=== Debug: Starting file detection ==="
|
echo "=== Debug: Starting file detection ==="
|
||||||
@ -41,10 +41,13 @@ jobs:
|
|||||||
if git rev-parse --verify HEAD~1 >/dev/null 2>&1; then
|
if git rev-parse --verify HEAD~1 >/dev/null 2>&1; then
|
||||||
echo "Parent commit exists; running git diff."
|
echo "Parent commit exists; running git diff."
|
||||||
CHANGED_FILES=$(git diff --name-only HEAD~1 2>/dev/null | grep -E '\.(c|bas)$' | head -1)
|
CHANGED_FILES=$(git diff --name-only HEAD~1 2>/dev/null | grep -E '\.(c|bas)$' | head -1)
|
||||||
|
DELETED_FILES=$(git diff --name-only --diff-filter=D HEAD~1 2>/dev/null | grep -E '\.(c|bas)$')
|
||||||
echo "Changed files from last commit: '${CHANGED_FILES}'"
|
echo "Changed files from last commit: '${CHANGED_FILES}'"
|
||||||
|
echo "Deleted files from last commit: '${DELETED_FILES}'"
|
||||||
else
|
else
|
||||||
echo "No parent commit; skipping diff."
|
echo "No parent commit; skipping diff."
|
||||||
CHANGED_FILES=""
|
CHANGED_FILES=""
|
||||||
|
DELETED_FILES=""
|
||||||
fi
|
fi
|
||||||
echo "=== Debug: Git diff check complete ==="
|
echo "=== Debug: Git diff check complete ==="
|
||||||
|
|
||||||
@ -57,7 +60,7 @@ jobs:
|
|||||||
echo "=== Debug: Fallback complete ==="
|
echo "=== Debug: Fallback complete ==="
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$CHANGED_FILES" ]; then
|
if [ -z "$CHANGED_FILES" ] && [ -z "$DELETED_FILES" ]; then
|
||||||
echo "No C/BAS files found; skipping workflow."
|
echo "No C/BAS files found; skipping workflow."
|
||||||
exit 0 # Graceful skip, no failure
|
exit 0 # Graceful skip, no failure
|
||||||
fi
|
fi
|
||||||
@ -67,11 +70,38 @@ jobs:
|
|||||||
echo "file=$CHANGED_FILES" >> $GITHUB_OUTPUT
|
echo "file=$CHANGED_FILES" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Extract member name (handle .c or .bas)
|
# Extract member name (handle .c or .bas)
|
||||||
|
if [ -n "$CHANGED_FILES" ]; then
|
||||||
EXT="${CHANGED_FILES##*.}"
|
EXT="${CHANGED_FILES##*.}"
|
||||||
BASE=$(basename "$CHANGED_FILES" ".$EXT")
|
BASE=$(basename "$CHANGED_FILES" ".$EXT")
|
||||||
echo "member=$BASE" >> $GITHUB_OUTPUT
|
echo "member=$BASE" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Process deleted files - convert to space-separated list of members
|
||||||
|
if [ -n "$DELETED_FILES" ]; then
|
||||||
|
DELETED_MEMBERS=""
|
||||||
|
for DFILE in $DELETED_FILES; do
|
||||||
|
DEXT="${DFILE##*.}"
|
||||||
|
DBASE=$(basename "$DFILE" ".$DEXT")
|
||||||
|
DELETED_MEMBERS="$DELETED_MEMBERS $DBASE"
|
||||||
|
done
|
||||||
|
echo "deleted_members=$DELETED_MEMBERS" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
echo "=== Debug: File detection complete ==="
|
echo "=== Debug: File detection complete ==="
|
||||||
|
|
||||||
|
- name: Delete removed members from PDS
|
||||||
|
if: ${{ steps.files.outputs.deleted_members != '' }}
|
||||||
|
run: |
|
||||||
|
echo "=== Debug: Starting deletion of removed members ==="
|
||||||
|
echo "Deleted members: ${{ steps.files.outputs.deleted_members }}"
|
||||||
|
for MEMBER in ${{ steps.files.outputs.deleted_members }}; do
|
||||||
|
echo "Deleting member: $MEMBER"
|
||||||
|
python3 delete_mvs_member.py "@05054.SRCLIB.C($MEMBER)"
|
||||||
|
done
|
||||||
|
echo "=== Debug: Deletion complete ==="
|
||||||
|
env:
|
||||||
|
MVS_BATCH_PASSWORD: ${{ vars.MVS_BATCH_PASSWORD }}
|
||||||
|
MVS_HOST: "oldcomputernerd.com"
|
||||||
|
|
||||||
- name: Upload to PDS and Submit JCL
|
- name: Upload to PDS and Submit JCL
|
||||||
if: ${{ steps.files.outputs.file != '' }}
|
if: ${{ steps.files.outputs.file != '' }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
96
delete_mvs_member.py
Normal file
96
delete_mvs_member.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Force temp files into a folder inside your project
|
||||||
|
custom_temp_dir = os.path.join(os.getcwd(), "tmp")
|
||||||
|
os.makedirs(custom_temp_dir, exist_ok=True)
|
||||||
|
tempfile.tempdir = custom_temp_dir
|
||||||
|
|
||||||
|
MVSHOST = "oldcomputernerd.com"
|
||||||
|
RDRPORT = 3505
|
||||||
|
MVS_PASSWORD = os.environ.get("MVS_BATCH_PASSWORD")
|
||||||
|
|
||||||
|
|
||||||
|
def create_delete_jcl(dataset_name, member_name):
|
||||||
|
"""Create JCL to delete a PDS member using IEHPROGM"""
|
||||||
|
|
||||||
|
jcl = f"""
|
||||||
|
//DELETE JOB (ACCT),'DELETE',
|
||||||
|
// USER=@05054,PASSWORD={MVS_PASSWORD},
|
||||||
|
// CLASS=A,MSGCLASS=H,NOTIFY=@05054
|
||||||
|
//DELMEM EXEC PGM=IEHPROGM
|
||||||
|
//SYSPRINT DD SYSOUT=*
|
||||||
|
//DD1 DD DSN={dataset_name},DISP=SHR
|
||||||
|
//SYSIN DD *
|
||||||
|
DELETE DSNAME={dataset_name},MEMBER={member_name}
|
||||||
|
/*
|
||||||
|
"""
|
||||||
|
return jcl
|
||||||
|
|
||||||
|
|
||||||
|
def delete_member(dataset_name, member_name, mvshost=MVSHOST):
|
||||||
|
"""Delete a member from MVS PDS"""
|
||||||
|
|
||||||
|
payload = create_delete_jcl(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:
|
||||||
|
result = subprocess.run(
|
||||||
|
['nc', '-w', '5', mvshost, str(RDRPORT)],
|
||||||
|
input=f.read(),
|
||||||
|
check=True,
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
print(f"Deleted {dataset_name}({member_name})")
|
||||||
|
if result.stdout:
|
||||||
|
print("JES response:", result.stdout.decode(errors='ignore').strip())
|
||||||
|
return 0
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Deletion failed: {e}")
|
||||||
|
print("stderr:", e.stderr.decode(errors='ignore'))
|
||||||
|
return 1
|
||||||
|
finally:
|
||||||
|
os.unlink(tmpfile_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: delete_mvs_member.py <pds_destination> [mvshost]")
|
||||||
|
print()
|
||||||
|
print("Arguments:")
|
||||||
|
print(" pds_destination - PDS destination as DATASET(MEMBER) (required)")
|
||||||
|
print(" mvshost - MVS host (optional, default: oldcomputernerd.com)")
|
||||||
|
print()
|
||||||
|
print("Examples:")
|
||||||
|
print(" delete_mvs_member.py '@05054.SRCLIB.C(SIEVE11)'")
|
||||||
|
print(" delete_mvs_member.py '@05054.SRCLIB.C(HELLO)' mainframe.example.com")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
destination = sys.argv[1]
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Optional host override
|
||||||
|
mvshost = sys.argv[2] if len(sys.argv) > 2 else MVSHOST
|
||||||
|
|
||||||
|
print(f"Deleting: {dataset_name}({member_name})")
|
||||||
|
print(f"Host: {mvshost}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
sys.exit(delete_member(dataset_name, member_name, mvshost))
|
||||||
Loading…
Reference in New Issue
Block a user