Initial commit: GitOps Status API
This commit is contained in:
commit
ec636a02b9
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@ -0,0 +1,9 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
.pytest_cache
|
||||
venv
|
||||
env
|
||||
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal 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
32
Dockerfile
Normal 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
86
README.md
Normal 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
172
app.py
Normal 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
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Flask==2.3.2
|
||||
Werkzeug==2.3.6
|
||||
Loading…
x
Reference in New Issue
Block a user