Initial commit
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
# Install system dependencies and GnuCOBOL compiler
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
gnucobol \
|
||||||
|
gcc \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install Python framework dependencies
|
||||||
|
RUN pip install --no-cache-dir fastapi uvicorn pyyaml
|
||||||
|
|
||||||
|
# Copy everything over
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["python", "main.py"]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
version: "1.0"
|
||||||
|
project: "legacy-finance-api"
|
||||||
|
endpoints:
|
||||||
|
- path: "/accounts/{id}"
|
||||||
|
method: "GET"
|
||||||
|
cobol_source: "src/fetch_account.cbl"
|
||||||
|
procedure: "GET_ACCOUNT_DATA"
|
||||||
|
arguments:
|
||||||
|
- name: "id"
|
||||||
|
type: "PIC X(10)"
|
||||||
|
source: "path"
|
||||||
|
response:
|
||||||
|
- name: "balance"
|
||||||
|
type: "PIC 9(7)V99"
|
||||||
|
- name: "status"
|
||||||
|
type: "PIC X(2)"
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
docker build -t cobol-rest-framework .
|
||||||
|
docker run -p 8000:8000 cobol-rest-framework
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
IDENTIFICATION DIVISION.
|
||||||
|
PROGRAM-ID. GET_ACCOUNT_DATA.
|
||||||
|
|
||||||
|
DATA DIVISION.
|
||||||
|
LINKAGE SECTION.
|
||||||
|
01 LK-ID PIC X(10).
|
||||||
|
01 LK-BALANCE PIC X(9).
|
||||||
|
01 LK-STATUS PIC X(2).
|
||||||
|
|
||||||
|
PROCEDURE DIVISION USING LK-ID LK-BALANCE LK-STATUS.
|
||||||
|
MAIN-LOGIC.
|
||||||
|
IF LK-ID = "42 "
|
||||||
|
MOVE "015004500" TO LK-BALANCE
|
||||||
|
MOVE "OK" TO LK-STATUS
|
||||||
|
ELSE
|
||||||
|
MOVE "000000000" TO LK-BALANCE
|
||||||
|
MOVE "NF" TO LK-STATUS
|
||||||
|
END-IF.
|
||||||
|
GOBACK.
|
||||||
Reference in New Issue
Block a user