Initial commit: GitOps Status API

This commit is contained in:
dvirlabs 2026-04-21 13:10:31 +03:00
commit ec636a02b9
6 changed files with 347 additions and 0 deletions

9
.dockerignore Normal file
View File

@ -0,0 +1,9 @@
__pycache__
*.pyc
*.pyo
.git
.gitignore
README.md
.pytest_cache
venv
env

46
.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Testing
.pytest_cache/
.coverage
htmlcov/
# Local
.env
.env.local
*.local
# Docker
Dockerfile.local
docker-compose.local.yml

32
Dockerfile Normal file
View File

@ -0,0 +1,32 @@
FROM python:3.11-alpine
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application
COPY app.py .
# Create data directory for status.json
RUN mkdir -p /data && chmod 777 /data
# Non-root user
RUN adduser -D -u 1000 apiuser && \
chown -R apiuser:apiuser /app /data
USER apiuser
# Health checks
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:5000/health')" || exit 1
EXPOSE 5000
ENV FLASK_ENV=production \
API_HOST=0.0.0.0 \
API_PORT=5000 \
STATUS_FILE=/data/status.json
CMD ["python", "app.py"]

86
README.md Normal file
View File

@ -0,0 +1,86 @@
# GitOps Status API
Simple Flask API for serving and updating GitOps status information.
## Features
- **GET /status.json** - Retrieve current status in JSON format
- **GET /api/status** - API endpoint to retrieve status
- **POST /api/status** - Update status with new data
- **GET /health** - Kubernetes liveness probe
- **GET /ready** - Kubernetes readiness probe
## Local Development
```bash
# Install dependencies
pip install -r requirements.txt
# Run the app
python app.py
# Test
curl http://localhost:5000/status.json
curl -X POST http://localhost:5000/api/status -H "Content-Type: application/json" -d '{"sync_status":"SYNCED"}'
```
## Docker Build
```bash
# Build the image
docker build -t gitops-status-api:1.0.0 .
# Run locally
docker run -it -p 5000:5000 -v /tmp/data:/data gitops-status-api:1.0.0
```
## Push to Harbor
```bash
# Login to Harbor
docker login harbor.your-domain.com
# Tag for Harbor
docker tag gitops-status-api:1.0.0 harbor.your-domain.com/gitops/status-api:1.0.0
docker tag gitops-status-api:1.0.0 harbor.your-domain.com/gitops/status-api:latest
# Push to Harbor
docker push harbor.your-domain.com/gitops/status-api:1.0.0
docker push harbor.your-domain.com/gitops/status-api:latest
```
## Environment Variables
- `API_HOST` - Listen address (default: 0.0.0.0)
- `API_PORT` - Listen port (default: 5000)
- `STATUS_FILE` - Path to status.json file (default: /data/status.json)
- `FLASK_ENV` - Flask environment (default: production)
## API Examples
### Get status
```bash
curl http://localhost:5000/status.json
curl http://localhost:5000/api/status
```
### Update status
```bash
curl -X POST http://localhost:5000/api/status \
-H "Content-Type: application/json" \
-d '{
"sync_status": "SYNCED",
"drift_count": 0,
"files": ["app1", "app2"]
}'
```
### Check health
```bash
curl http://localhost:5000/health
curl http://localhost:5000/ready
```
## Version
1.0.0

172
app.py Normal file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""
GitOps Status Server API
Simple Flask API for serving and updating status.json
Listens on port 5000 and handles GET/POST requests
"""
import os
import json
import logging
from flask import Flask, request, jsonify
from datetime import datetime
app = Flask(__name__)
# Configuration from environment
STATUS_FILE = os.environ.get('STATUS_FILE', '/data/status.json')
API_HOST = os.environ.get('API_HOST', '0.0.0.0')
API_PORT = int(os.environ.get('API_PORT', 5000))
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def load_status():
"""Load the current status from file"""
try:
if os.path.exists(STATUS_FILE):
with open(STATUS_FILE, 'r') as f:
return json.load(f)
else:
logger.warning(f"Status file not found: {STATUS_FILE}")
return {
"repo": "unknown",
"server": "unknown",
"sync_status": "UNKNOWN",
"drift_count": 0,
"files": [],
"last_check": ""
}
except Exception as e:
logger.error(f"Error loading status: {e}")
return {}
def save_status(status):
"""Save the status to file"""
try:
# Ensure directory exists
os.makedirs(os.path.dirname(STATUS_FILE), exist_ok=True)
# Write with proper formatting
with open(STATUS_FILE, 'w') as f:
json.dump(status, f, indent=2)
logger.info(f"Status updated: {status.get('repo', 'unknown')}/{status.get('server', 'unknown')} -> {status.get('sync_status', 'UNKNOWN')}")
return True
except Exception as e:
logger.error(f"Error saving status: {e}")
return False
@app.route('/status.json', methods=['GET'])
def get_status():
"""GET /status.json - Retrieve current status"""
try:
status = load_status()
if status:
return jsonify(status), 200
else:
return jsonify({"error": "Failed to load status"}), 500
except Exception as e:
logger.error(f"Error in GET /status.json: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/status', methods=['GET', 'POST', 'OPTIONS'])
def api_status():
"""
GET /api/status - Retrieve current status
POST /api/status - Update status with new data
"""
if request.method == 'OPTIONS':
return '', 204
if request.method == 'GET':
status = load_status()
return jsonify(status), 200
if request.method == 'POST':
try:
incoming_data = request.get_json()
if not incoming_data:
return jsonify({"error": "No JSON data provided"}), 400
# Load current status
status = load_status()
# Update with incoming data (merge)
status.update(incoming_data)
# Add/update timestamp if not present
if 'last_check' not in status or not status['last_check']:
status['last_check'] = datetime.utcnow().isoformat() + 'Z'
# Save updated status
if save_status(status):
return jsonify({
"success": True,
"message": "Status updated successfully",
"status": status
}), 200
else:
return jsonify({
"error": "Failed to save status"
}), 500
except json.JSONDecodeError:
return jsonify({"error": "Invalid JSON"}), 400
except Exception as e:
logger.error(f"Error in POST /api/status: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/health', methods=['GET'])
def health():
"""GET /health - Kubernetes liveness probe"""
return jsonify({"status": "healthy"}), 200
@app.route('/ready', methods=['GET'])
def ready():
"""GET /ready - Kubernetes readiness probe"""
try:
# Try to access the status file
if os.path.exists(STATUS_FILE):
status = load_status()
if isinstance(status, dict):
return jsonify({"status": "ready"}), 200
return jsonify({"status": "not_ready", "reason": "status file not accessible"}), 503
except Exception as e:
logger.error(f"Readiness check failed: {e}")
return jsonify({"status": "not_ready", "error": str(e)}), 503
@app.route('/', methods=['GET'])
def root():
"""GET / - Simple info endpoint"""
return jsonify({
"name": "GitOps Status API",
"version": "1.0.0",
"endpoints": {
"GET /status.json": "Retrieve current status",
"GET /api/status": "Retrieve current status (JSON API)",
"POST /api/status": "Update status with new data",
"GET /health": "Liveness probe",
"GET /ready": "Readiness probe"
}
}), 200
if __name__ == '__main__':
logger.info(f"Starting GitOps Status API on {API_HOST}:{API_PORT}")
logger.info(f"Status file location: {STATUS_FILE}")
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(STATUS_FILE) or '.', exist_ok=True)
app.run(host=API_HOST, port=API_PORT, debug=False, threaded=True)

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
Flask==2.3.2
Werkzeug==2.3.6