Jelajahi Sumber

Version 1.0

Marius 5 bulan lalu
induk
melakukan
d008d2028f
6 mengubah file dengan 180 tambahan dan 0 penghapusan
  1. 37 0
      Dockerfile
  2. 9 0
      README.md
  3. 8 0
      defaults/config.yaml
  4. 3 0
      defaults/hello_world.py
  5. 39 0
      log_formatter.py
  6. 84 0
      manager.py

+ 37 - 0
Dockerfile

@@ -0,0 +1,37 @@
+FROM python:3.12-slim
+
+# 1. Install system dependencies
+RUN apt-get update && apt-get install -y --no-install-recommends \
+    curl \
+    && rm -rf /var/lib/apt/lists/*
+
+# 2. Install Python deps
+RUN pip install --upgrade pip
+RUN pip install --no-cache-dir pyyaml
+
+# 3. Install Supercronic
+ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 \
+    SUPERCRONIC=supercronic-linux-amd64 \
+    SUPERCRONIC_SHA1SUM=cd48d45c4b10f3f0bfdd3a57d054cd05ac96812b
+
+RUN curl -fsSLO "$SUPERCRONIC_URL" \
+    && echo "${SUPERCRONIC_SHA1SUM}  ${SUPERCRONIC}" | sha1sum -c - \
+    && chmod +x "$SUPERCRONIC" \
+    && mv "$SUPERCRONIC" /usr/local/bin/supercronic
+
+# 4. Setup App Logic
+WORKDIR /app
+COPY manager.py /app/manager.py
+COPY log_formatter.py /app/log_formatter.py
+
+# 5. Setup Defaults
+# We create a separate folder to hold the "template" files
+RUN mkdir /app/defaults
+COPY defaults/config.yaml /app/defaults/config.yaml
+COPY defaults/hello_world.py /app/defaults/hello_world.py
+
+# 6. Prepare the mount point
+RUN mkdir /scripts
+VOLUME ["/scripts"]
+
+ENTRYPOINT ["python3", "-u", "/app/manager.py"]

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
+Current Features:
+- run python-scripts in the /scripts
+- install dependencies
+- config via config-file in /scripts
+- auto-populate /scripts if empty
+- hello_world.py demo in scripts folder
+- scripts print to container logs
+
+Planned Features:

+ 8 - 0
defaults/config.yaml

@@ -0,0 +1,8 @@
+# 1. List libraries to install at startup
+dependencies:
+  - datetime
+
+# 2. Define jobs (Script Name + Cron Schedule)
+jobs:
+  - script: "hello_world.py"
+    schedule: "* * * * *"      # Runs every minute

+ 3 - 0
defaults/hello_world.py

@@ -0,0 +1,3 @@
+import datetime
+print("Hello world")
+print(f"the current date & time is: {datetime.datetime.now()}")

+ 39 - 0
log_formatter.py

@@ -0,0 +1,39 @@
+import sys
+import re
+
+# Regex zum Finden des Script-Namens im Command
+# Sucht nach: job.command="... /scripts/(DATEINAME)"
+script_pattern = re.compile(r'job\.command=".*?/scripts/([^"]+)"')
+
+# Regex zum Finden der eigentlichen Nachricht
+# Sucht nach: msg="NACHRICHT"
+msg_pattern = re.compile(r'msg="(.*?)"')
+
+def clean_log():
+    # Liest Standard-Input (Pipe von Supercronic)
+    for line in sys.stdin:
+        # Wir interessieren uns nur für Zeilen, die von unseren Scripts kommen
+        # (channel=stdout oder channel=stderr)
+        if 'channel=stdout' in line or 'channel=stderr' in line:
+            
+            # Script Namen extrahieren
+            script_match = script_pattern.search(line)
+            script_name = script_match.group(1) if script_match else "Unknown"
+            
+            # Nachricht extrahieren
+            msg_match = msg_pattern.search(line)
+            message = msg_match.group(1) if msg_match else line.strip()
+            
+            # Gewünschtes Format ausgeben: [script.py] Nachricht
+            print(f"[{script_name}] {message}")
+            
+            # WICHTIG: Sofort flushen, damit Logs nicht verzögert erscheinen
+            sys.stdout.flush()
+            
+        elif 'level=error' in line or 'level=warning' in line:
+            # System-Fehler von Supercronic (z.B. Syntaxfehler im Cron) trotzdem anzeigen
+            print(f"[SYSTEM] {line.strip()}")
+            sys.stdout.flush()
+
+if __name__ == "__main__":
+    clean_log()

+ 84 - 0
manager.py

@@ -0,0 +1,84 @@
+import os
+import sys
+import subprocess
+import yaml
+import shutil
+
+# Constants
+SCRIPTS_DIR = "/scripts"
+DEFAULTS_DIR = "/app/defaults"
+CONFIG_PATH = os.path.join(SCRIPTS_DIR, "config.yaml")
+CRONTAB_PATH = "/app/generated_crontab"
+
+def initialize_volume_if_empty():
+    """Copies default files to /scripts if config.yaml is missing."""
+    if not os.path.exists(CONFIG_PATH):
+        print("---------------------------------")
+        print("No config found in volume. Initializing with defaults...")
+        
+        # Ensure directory exists
+        os.makedirs(SCRIPTS_DIR, exist_ok=True)
+        
+        # Copy all files from defaults to scripts
+        if os.path.exists(DEFAULTS_DIR):
+            for filename in os.listdir(DEFAULTS_DIR):
+                src = os.path.join(DEFAULTS_DIR, filename)
+                dst = os.path.join(SCRIPTS_DIR, filename)
+                if os.path.isfile(src):
+                    shutil.copy2(src, dst)
+                    print(f"  -> Created: {filename}")
+        print("Initialization complete.")
+        print("---------------------------------")
+    else:
+        print(f"Found existing config at {CONFIG_PATH}. Using it.")
+
+def install_dependencies(deps):
+    if not deps:
+        return
+    print(f"Installing {len(deps)} dependencies...")
+    subprocess.check_call([sys.executable, "-m", "pip", "install", "--no-cache-dir"] + deps)
+
+def generate_crontab(jobs):
+    print(f"Scheduling {len(jobs)} jobs...")
+    lines = []
+    for job in jobs:
+        schedule = job['schedule']
+        script = job['script']
+        # Use -u for unbuffered output
+        command = f"python3 -u {SCRIPTS_DIR}/{script}"
+        lines.append(f"{schedule} {command}")
+        print(f"  -> Loaded: {script} [{schedule}]")
+
+    with open(CRONTAB_PATH, "w") as f:
+        f.write("\n".join(lines) + "\n")
+
+def main():
+    # 1. Initialize Volume (The new feature)
+    initialize_volume_if_empty()
+
+    # 2. Read Config
+    if not os.path.exists(CONFIG_PATH):
+        print(f"Error: {CONFIG_PATH} still not found after initialization.")
+        sys.exit(1)
+
+    with open(CONFIG_PATH, "r") as f:
+        config = yaml.safe_load(f)
+
+    # 3. Setup Environment
+    install_dependencies(config.get("dependencies", []))
+    generate_crontab(config.get("jobs", []))
+
+    # 4. Start Supercronic
+    print("Starting Scheduler...")
+    print("--------------------------------------------")
+    sys.stdout.flush()
+
+    # Wir starten Supercronic und leiten stderr (wo Logs sind) an stdout weiter
+    # und pipen das Ganze in unseren log_formatter.py
+    cmd = f"supercronic {CRONTAB_PATH} 2>&1 | python3 -u /app/log_formatter.py"
+    
+    # shell=True ist hier notwendig für die Pipe "|"
+    subprocess.call(cmd, shell=True)
+
+if __name__ == "__main__":
+    main()