Spaces:
Sleeping
Sleeping
Upload 7 files
Browse files- Dockerfile +29 -16
- config.sh +39 -0
- entrypoint.sh +42 -0
- nginx.conf +65 -0
- refresh_service.py +76 -0
- requirements.txt +7 -2
- supervisord.conf +38 -0
Dockerfile
CHANGED
|
@@ -1,24 +1,37 @@
|
|
| 1 |
-
FROM
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
USER airflow
|
| 10 |
|
| 11 |
-
ENV
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
| 21 |
|
| 22 |
EXPOSE 7860
|
| 23 |
|
| 24 |
-
ENTRYPOINT ["/
|
|
|
|
| 1 |
+
FROM ubuntu:22.04
|
| 2 |
|
| 3 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 4 |
+
ENV AIRFLOW_HOME=/opt/airflow
|
| 5 |
|
| 6 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 7 |
+
python3 \
|
| 8 |
+
python3-pip \
|
| 9 |
+
python3-venv \
|
| 10 |
+
nginx \
|
| 11 |
+
git \
|
| 12 |
+
supervisor \
|
| 13 |
+
curl \
|
| 14 |
+
&& rm -rf /var/lib/apt/lists/* \
|
| 15 |
+
&& useradd -m -u 1000 airflow
|
| 16 |
+
|
| 17 |
+
RUN mkdir -p /var/log/supervisor /var/log/nginx /run/nginx /var/lib/nginx /opt/airflow/dags \
|
| 18 |
+
&& chown -R airflow:airflow /opt/airflow /var/log/supervisor /var/log/nginx /run/nginx /var/lib/nginx
|
| 19 |
|
| 20 |
USER airflow
|
| 21 |
|
| 22 |
+
ENV PATH="/home/airflow/.local/bin:${PATH}"
|
| 23 |
+
|
| 24 |
+
COPY --chown=airflow:airflow requirements.txt /tmp/requirements.txt
|
| 25 |
+
RUN pip install --no-cache-dir -r /tmp/requirements.txt
|
| 26 |
+
|
| 27 |
+
COPY --chown=airflow:airflow config.sh /config.sh
|
| 28 |
+
COPY --chown=airflow:airflow supervisord.conf /etc/supervisord.conf
|
| 29 |
+
COPY --chown=airflow:airflow nginx.conf /etc/nginx/nginx.conf
|
| 30 |
+
COPY --chown=airflow:airflow entrypoint.sh /entrypoint.sh
|
| 31 |
+
COPY --chown=airflow:airflow refresh_service.py /refresh_service.py
|
| 32 |
+
|
| 33 |
+
RUN chmod +x /entrypoint.sh /config.sh
|
| 34 |
|
| 35 |
EXPOSE 7860
|
| 36 |
|
| 37 |
+
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
config.sh
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# ===========================================
|
| 3 |
+
# Airflow Configuration - SINGLE SOURCE OF TRUTH
|
| 4 |
+
# ===========================================
|
| 5 |
+
# All configurable values are defined here with defaults
|
| 6 |
+
# Override by setting environment variables or HF Secrets
|
| 7 |
+
|
| 8 |
+
# ---- Directory Paths ----
|
| 9 |
+
export AIRFLOW_HOME="${AIRFLOW_HOME:-/opt/airflow}"
|
| 10 |
+
export DAGS_DIR="${AIRFLOW_HOME}/dags"
|
| 11 |
+
export LOGS_DIR="${AIRFLOW_HOME}/logs"
|
| 12 |
+
|
| 13 |
+
# ---- Database Connection (Set via HF Secret for production) ----
|
| 14 |
+
export AIRFLOW__DATABASE__SQL_ALCHEMY_CONN="${AIRFLOW__DATABASE__SQL_ALCHEMY_CONN:-postgresql://postgres.ffetkispaxytayrnmxvj:uKInpRkBXsCUfVg1@aws-1-ap-south-1.pooler.supabase.com:5432/postgres}"
|
| 15 |
+
|
| 16 |
+
# ---- DAG Repository ----
|
| 17 |
+
export DAG_REPO_URL="${DAG_REPO_URL:-https://github.com/subhamgiri460/myworkflows.git}"
|
| 18 |
+
export DAG_REPO_BRANCH="${DAG_REPO_BRANCH:-main}"
|
| 19 |
+
|
| 20 |
+
# ---- Admin Credentials (Set via HF Secrets for production) ----
|
| 21 |
+
export AIRFLOW_ADMIN_USER="${AIRFLOW_ADMIN_USER:-admin}"
|
| 22 |
+
export AIRFLOW_ADMIN_PASSWORD="${AIRFLOW_ADMIN_PASSWORD:-admin}"
|
| 23 |
+
|
| 24 |
+
# ---- Internal Ports ----
|
| 25 |
+
export AIRFLOW_WEB_PORT="${AIRFLOW_WEB_PORT:-8080}"
|
| 26 |
+
export REFRESH_SERVICE_PORT="${REFRESH_SERVICE_PORT:-5000}"
|
| 27 |
+
export NGINX_PORT="${NGINX_PORT:-7860}"
|
| 28 |
+
|
| 29 |
+
# ---- Airflow Core Settings ----
|
| 30 |
+
export AIRFLOW__CORE__LOAD_EXAMPLES="${AIRFLOW__CORE__LOAD_EXAMPLES:-False}"
|
| 31 |
+
export AIRFLOW__CORE__EXECUTOR="${AIRFLOW__CORE__EXECUTOR:-LocalExecutor}"
|
| 32 |
+
|
| 33 |
+
# ---- Airflow Webserver Settings ----
|
| 34 |
+
export AIRFLOW__WEBSERVER__WEB_SERVER_PORT="${AIRFLOW_WEB_PORT}"
|
| 35 |
+
export AIRFLOW__WEBSERVER__WEB_SERVER_MASTER_TIMEOUT="${AIRFLOW__WEBSERVER__WEB_SERVER_MASTER_TIMEOUT:-300}"
|
| 36 |
+
export AIRFLOW__WEBSERVER__WORKER_CLASS="${AIRFLOW__WEBSERVER__WORKER_CLASS:-gevent}"
|
| 37 |
+
|
| 38 |
+
# ---- Airflow Scheduler Settings ----
|
| 39 |
+
export AIRFLOW__SCHEDULER__DAG_DIR_LIST_INTERVAL="${AIRFLOW__SCHEDULER__DAG_DIR_LIST_INTERVAL:-30}"
|
entrypoint.sh
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -e
|
| 3 |
+
|
| 4 |
+
echo "=== Loading configuration ==="
|
| 5 |
+
source /config.sh
|
| 6 |
+
|
| 7 |
+
echo "Configuration:"
|
| 8 |
+
echo " DAG_REPO_URL: ${DAG_REPO_URL}"
|
| 9 |
+
echo " DAG_REPO_BRANCH: ${DAG_REPO_BRANCH}"
|
| 10 |
+
echo " DAGS_DIR: ${DAGS_DIR}"
|
| 11 |
+
echo " AIRFLOW_WEB_PORT: ${AIRFLOW_WEB_PORT}"
|
| 12 |
+
echo " REFRESH_SERVICE_PORT: ${REFRESH_SERVICE_PORT}"
|
| 13 |
+
|
| 14 |
+
echo ""
|
| 15 |
+
echo "=== Syncing DAG repository ==="
|
| 16 |
+
if [ ! -d "${DAGS_DIR}/.git" ]; then
|
| 17 |
+
rm -rf "${DAGS_DIR:?}"/* 2>/dev/null || true
|
| 18 |
+
git clone --depth 1 --branch "${DAG_REPO_BRANCH}" "${DAG_REPO_URL}" "${DAGS_DIR}"
|
| 19 |
+
echo "Repository cloned successfully"
|
| 20 |
+
else
|
| 21 |
+
cd "${DAGS_DIR}"
|
| 22 |
+
git fetch origin "${DAG_REPO_BRANCH}" && git reset --hard "origin/${DAG_REPO_BRANCH}"
|
| 23 |
+
echo "Repository updated successfully"
|
| 24 |
+
fi
|
| 25 |
+
|
| 26 |
+
echo ""
|
| 27 |
+
echo "=== Running database migrations ==="
|
| 28 |
+
airflow db migrate || true
|
| 29 |
+
|
| 30 |
+
echo ""
|
| 31 |
+
echo "=== Creating admin user ==="
|
| 32 |
+
airflow users create \
|
| 33 |
+
--username "${AIRFLOW_ADMIN_USER}" \
|
| 34 |
+
--firstname Admin \
|
| 35 |
+
--lastname User \
|
| 36 |
+
--role Admin \
|
| 37 |
+
--email admin@example.com \
|
| 38 |
+
--password "${AIRFLOW_ADMIN_PASSWORD}" || true
|
| 39 |
+
|
| 40 |
+
echo ""
|
| 41 |
+
echo "=== Starting services ==="
|
| 42 |
+
exec /usr/bin/supervisord -c /etc/supervisord.conf
|
nginx.conf
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
worker_processes 1;
|
| 2 |
+
error_log /var/log/nginx/error.log warn;
|
| 3 |
+
pid /run/nginx/nginx.pid;
|
| 4 |
+
|
| 5 |
+
events {
|
| 6 |
+
worker_connections 1024;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
http {
|
| 10 |
+
include /etc/nginx/mime.types;
|
| 11 |
+
default_type application/octet-stream;
|
| 12 |
+
|
| 13 |
+
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
| 14 |
+
'$status $body_bytes_sent "$http_referer" "$http_user_agent"';
|
| 15 |
+
|
| 16 |
+
access_log /var/log/nginx/access.log main;
|
| 17 |
+
sendfile on;
|
| 18 |
+
keepalive_timeout 65;
|
| 19 |
+
|
| 20 |
+
client_body_temp_path /run/nginx/client_temp;
|
| 21 |
+
proxy_temp_path /run/nginx/proxy_temp;
|
| 22 |
+
fastcgi_temp_path /run/nginx/fastcgi_temp;
|
| 23 |
+
uwsgi_temp_path /run/nginx/uwsgi_temp;
|
| 24 |
+
scgi_temp_path /run/nginx/scgi_temp;
|
| 25 |
+
|
| 26 |
+
upstream airflow {
|
| 27 |
+
server 127.0.0.1:8080;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
upstream refresh {
|
| 31 |
+
server 127.0.0.1:5000;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
server {
|
| 35 |
+
listen 7860;
|
| 36 |
+
server_name _;
|
| 37 |
+
client_max_body_size 100M;
|
| 38 |
+
|
| 39 |
+
location /refresh {
|
| 40 |
+
proxy_pass http://refresh;
|
| 41 |
+
proxy_set_header Host $host;
|
| 42 |
+
proxy_read_timeout 120s;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
location /config {
|
| 46 |
+
proxy_pass http://refresh;
|
| 47 |
+
proxy_set_header Host $host;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
location /health {
|
| 51 |
+
proxy_pass http://refresh;
|
| 52 |
+
proxy_set_header Host $host;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
location / {
|
| 56 |
+
proxy_pass http://airflow;
|
| 57 |
+
proxy_set_header Host $host;
|
| 58 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 59 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 60 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
| 61 |
+
proxy_read_timeout 300s;
|
| 62 |
+
proxy_connect_timeout 75s;
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
}
|
refresh_service.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from flask import Flask, jsonify
|
| 3 |
+
import subprocess
|
| 4 |
+
|
| 5 |
+
app = Flask(__name__)
|
| 6 |
+
|
| 7 |
+
DAGS_DIR = os.environ.get('AIRFLOW_HOME', '/opt/airflow') + '/dags'
|
| 8 |
+
DAG_REPO_URL = os.environ.get('DAG_REPO_URL', 'https://github.com/subhamgiri460/myworkflows.git')
|
| 9 |
+
DAG_REPO_BRANCH = os.environ.get('DAG_REPO_BRANCH', 'main')
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@app.route('/refresh', methods=['GET', 'POST'])
|
| 13 |
+
def refresh_dags():
|
| 14 |
+
try:
|
| 15 |
+
if os.path.exists(os.path.join(DAGS_DIR, '.git')):
|
| 16 |
+
fetch_result = subprocess.run(
|
| 17 |
+
['git', 'fetch', 'origin', DAG_REPO_BRANCH],
|
| 18 |
+
cwd=DAGS_DIR, capture_output=True, text=True, timeout=60
|
| 19 |
+
)
|
| 20 |
+
if fetch_result.returncode != 0:
|
| 21 |
+
return jsonify({
|
| 22 |
+
'status': 'error',
|
| 23 |
+
'message': f'Git fetch failed: {fetch_result.stderr}'
|
| 24 |
+
}), 500
|
| 25 |
+
|
| 26 |
+
reset_result = subprocess.run(
|
| 27 |
+
['git', 'reset', '--hard', f'origin/{DAG_REPO_BRANCH}'],
|
| 28 |
+
cwd=DAGS_DIR, capture_output=True, text=True, timeout=60
|
| 29 |
+
)
|
| 30 |
+
result = reset_result
|
| 31 |
+
action = 'updated'
|
| 32 |
+
else:
|
| 33 |
+
result = subprocess.run(
|
| 34 |
+
['git', 'clone', '--depth', '1', '--branch', DAG_REPO_BRANCH, DAG_REPO_URL, DAGS_DIR],
|
| 35 |
+
capture_output=True, text=True, timeout=120
|
| 36 |
+
)
|
| 37 |
+
action = 'cloned'
|
| 38 |
+
|
| 39 |
+
if result.returncode == 0:
|
| 40 |
+
return jsonify({
|
| 41 |
+
'status': 'success',
|
| 42 |
+
'message': f'DAGs {action} successfully',
|
| 43 |
+
'repo': DAG_REPO_URL,
|
| 44 |
+
'branch': DAG_REPO_BRANCH
|
| 45 |
+
}), 200
|
| 46 |
+
else:
|
| 47 |
+
return jsonify({
|
| 48 |
+
'status': 'error',
|
| 49 |
+
'message': result.stderr or 'Unknown error'
|
| 50 |
+
}), 500
|
| 51 |
+
|
| 52 |
+
except subprocess.TimeoutExpired:
|
| 53 |
+
return jsonify({'status': 'error', 'message': 'Operation timed out'}), 500
|
| 54 |
+
except Exception as e:
|
| 55 |
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
@app.route('/health')
|
| 59 |
+
def health():
|
| 60 |
+
return jsonify({'status': 'healthy'}), 200
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@app.route('/config')
|
| 64 |
+
def show_config():
|
| 65 |
+
return jsonify({
|
| 66 |
+
'dags_dir': DAGS_DIR,
|
| 67 |
+
'repo_url': DAG_REPO_URL,
|
| 68 |
+
'branch': DAG_REPO_BRANCH,
|
| 69 |
+
'airflow_web_port': os.environ.get('AIRFLOW_WEB_PORT', '8080'),
|
| 70 |
+
'refresh_service_port': os.environ.get('REFRESH_SERVICE_PORT', '5000')
|
| 71 |
+
}), 200
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
if __name__ == '__main__':
|
| 75 |
+
port = int(os.environ.get('REFRESH_SERVICE_PORT', 5000))
|
| 76 |
+
app.run(host='0.0.0.0', port=port)
|
requirements.txt
CHANGED
|
@@ -1,2 +1,7 @@
|
|
| 1 |
-
apache-airflow
|
| 2 |
-
apache-airflow-providers-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
apache-airflow[postgres]==2.10.3
|
| 2 |
+
apache-airflow-providers-postgres
|
| 3 |
+
apache-airflow-providers-ssh
|
| 4 |
+
flask
|
| 5 |
+
gunicorn
|
| 6 |
+
gevent
|
| 7 |
+
requests
|
supervisord.conf
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[supervisord]
|
| 2 |
+
nodaemon=true
|
| 3 |
+
user=airflow
|
| 4 |
+
logfile=/var/log/supervisor/supervisord.log
|
| 5 |
+
pidfile=/run/nginx/supervisord.pid
|
| 6 |
+
loglevel=info
|
| 7 |
+
|
| 8 |
+
[program:nginx]
|
| 9 |
+
command=/usr/sbin/nginx -g "daemon off;"
|
| 10 |
+
autostart=true
|
| 11 |
+
autorestart=true
|
| 12 |
+
stderr_logfile=/var/log/supervisor/nginx.err.log
|
| 13 |
+
stdout_logfile=/var/log/supervisor/nginx.out.log
|
| 14 |
+
priority=5
|
| 15 |
+
|
| 16 |
+
[program:airflow-webserver]
|
| 17 |
+
command=airflow webserver
|
| 18 |
+
autostart=true
|
| 19 |
+
autorestart=true
|
| 20 |
+
stderr_logfile=/var/log/supervisor/webserver.err.log
|
| 21 |
+
stdout_logfile=/var/log/supervisor/webserver.out.log
|
| 22 |
+
priority=20
|
| 23 |
+
|
| 24 |
+
[program:airflow-scheduler]
|
| 25 |
+
command=airflow scheduler
|
| 26 |
+
autostart=true
|
| 27 |
+
autorestart=true
|
| 28 |
+
stderr_logfile=/var/log/supervisor/scheduler.err.log
|
| 29 |
+
stdout_logfile=/var/log/supervisor/scheduler.out.log
|
| 30 |
+
priority=30
|
| 31 |
+
|
| 32 |
+
[program:refresh-service]
|
| 33 |
+
command=gunicorn --bind 127.0.0.1:5000 --workers 1 --worker-class gevent --chdir / refresh_service:app
|
| 34 |
+
autostart=true
|
| 35 |
+
autorestart=true
|
| 36 |
+
stderr_logfile=/var/log/supervisor/refresh.err.log
|
| 37 |
+
stdout_logfile=/var/log/supervisor/refresh.out.log
|
| 38 |
+
priority=10
|