Initial commit

This commit is contained in:
2026-06-09 19:29:34 +02:00
commit 2d8602609c
5 changed files with 158 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
import os
import subprocess
import ctypes
import yaml
from fastapi import FastAPI, HTTPException
app = FastAPI(title="COBOL REST Framework")
# 1. Load the Configuration
with open("api.yaml", "r") as f:
config = yaml.safe_load(f)
# 2. On-the-fly Compiler Pipeline
# This makes it completely system independent!
COMPILED_LIBS = {}
for endpoint in config["endpoints"]:
cbl_source = endpoint["cobol_source"]
# Convert 'src/fetch_account.cbl' -> '/tmp/fetch_account.so'
so_name = os.path.basename(cbl_source).replace(".cbl", ".so")
so_path = os.path.join("/tmp", so_name)
print(f"🛠️ Compiling legacy asset {cbl_source} to {so_path}...")
# Use standard Linux GnuCOBOL compilation flags
result = subprocess.run(["cobc", "-b", "-o", so_path, cbl_source], capture_output=True, text=True)
if result.returncode != 0:
print(f"❌ Compilation Failed:\n{result.stderr}")
exit(1)
COMPILED_LIBS[cbl_source] = so_path
# Initialize GnuCOBOL environment globally
try:
libcob = ctypes.CDLL("libcob.so", mode=ctypes.RTLD_GLOBAL)
libcob.cob_init()
except OSError:
pass
def call_cobol(so_path, procedure, account_id):
"""Dynamically loads compiled COBOL libraries and maps network payloads to buffers"""
# 1. Load the library module
cobol_lib = ctypes.CDLL(so_path)
# 2. Use GnuCOBOL's official runtime resolver to find our PROCEDURE/PROGRAM-ID
# This acts as the bulletproof bridge between Linux and COBOL symbols
try:
libcob = ctypes.CDLL("libcob.so", mode=ctypes.RTLD_GLOBAL)
libcob.cob_resolve.restype = ctypes.c_void_p
func_ptr = libcob.cob_resolve(procedure.encode('utf-8'))
except Exception:
func_ptr = None
# Fallback to direct symbol reading if cob_resolve isn't bound yet
if not func_ptr:
for name in [procedure, procedure.lower(), procedure.upper()]:
if hasattr(cobol_lib, name):
func_ptr = ctypes.cast(getattr(cobol_lib, name), ctypes.c_void_p).value
break
if not func_ptr:
raise HTTPException(
status_code=500,
detail=f"COBOL Procedure symbol '{procedure}' could not be resolved."
)
# Cast the resolved pointer into a callable Python FFI function
cobol_func = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p)(func_ptr)
# 3. Map Input Argument: PIC X(10)
padded_id = account_id.ljust(10)[:10].encode('ascii')
id_buf = ctypes.create_string_buffer(padded_id, 10)
# 4. Allocate Output Buffers matching the YAML spec sizes
balance_buf = ctypes.create_string_buffer(9) # 9 bytes
status_buf = ctypes.create_string_buffer(2) # 2 bytes
# 5. Call out to the compiled COBOL logic
cobol_func(id_buf, balance_buf, status_buf)
# 6. Decode response strings
status = status_buf.value.decode('ascii').strip()
if status == "NF":
raise HTTPException(status_code=404, detail="Account not found in COBOL storage")
raw_balance = balance_buf.value.decode('ascii').strip()
# Turn fixed-length string '015004500' -> 1500.45
balance = float(raw_balance[:7] + "." + raw_balance[7:9])
return {"balance": balance, "status": status}
# 3. Dynamic API Route Builder
for endpoint in config["endpoints"]:
if endpoint["method"] == "GET" and endpoint["path"] == "/accounts/{id}":
@app.get(endpoint["path"])
def get_account(id: str):
so_file = COMPILED_LIBS[endpoint["cobol_source"]]
proc_name = endpoint["procedure"]
return call_cobol(so_file, proc_name, id)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)