Skip to content

Commit 65bb060

Browse files
author
Gpshfrd
committed
lab done
1 parent 47df9b9 commit 65bb060

13 files changed

Lines changed: 664 additions & 10 deletions

File tree

‎app_python/Dockerfile‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
FROM python:3.13-slim
1+
FROM dockerhub.timeweb.cloud/library/python:3.13-slim
22

33
ENV PYTHONDONTWRITEBYTECODE=1
44
ENV PYTHONUNBUFFERED=1
55

6-
# Non-root user setup
7-
RUN useradd --create-home appuser
6+
RUN useradd --create-home appuser && \
7+
mkdir -p /app/data /app/config && \
8+
chown -R appuser:appuser /app
89

910
WORKDIR /app
1011

1112
COPY requirements.txt .
1213

1314
RUN pip install --no-cache-dir -r requirements.txt
1415

15-
# Copy application code
1616
COPY app.py .
1717

18-
#Swithh to non-root user
1918
USER appuser
2019

2120
EXPOSE 8000

‎app_python/app.py‎

Lines changed: 153 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import logging
55
import sys
66
import time
7+
import json
8+
from pathlib import Path
79
from datetime import datetime, timezone
810
from pythonjsonlogger import jsonlogger
911

@@ -21,6 +23,10 @@
2123
SERVICE_DESCRIPTION = "DevOps course info service"
2224
FRAMEWORK = "Flask"
2325

26+
CONFIG_FILE_PATH = os.getenv("CONFIG_FILE_PATH", "/config/config.json")
27+
DATA_DIR = os.getenv("DATA_DIR", "/data")
28+
VISITS_FILE = Path(DATA_DIR) / "visits.txt"
29+
2430
### App and logging
2531
logger = logging.getLogger()
2632
logger.setLevel(logging.INFO)
@@ -57,6 +63,87 @@
5763
"Number of HTTP requests in progress"
5864
)
5965

66+
visits_total = Counter(
67+
"visits_total",
68+
"Total number of visits to the application"
69+
)
70+
71+
def load_config_from_configmap():
72+
"""Load configuration from ConfigMap mounted file"""
73+
default_config = {
74+
"app_name": "DevOps Info Service",
75+
"environment": "development",
76+
"features": {
77+
"visits_tracking": True,
78+
"metrics_enabled": True
79+
},
80+
"log_level": "INFO",
81+
"max_visits_display": 1000000,
82+
"welcome_message": "Welcome to DevOps Info Service!"
83+
}
84+
85+
try:
86+
if os.path.exists(CONFIG_FILE_PATH):
87+
with open(CONFIG_FILE_PATH, "r") as f:
88+
config = json.load(f)
89+
default_config.update(config)
90+
logger.info("Configuration loaded from {CONFIG_FILE_PATH}")
91+
else:
92+
logger.warning(f"Config file {CONFIG_FILE_PATH} not found. Using default configuration.")
93+
except Exception as e:
94+
logger.error(f"Error loading config: {e}.")
95+
return default_config
96+
97+
APP_CONFIG = load_config_from_configmap()
98+
99+
log_level = getattr(logging, APP_CONFIG.get("log_level", "INFO"))
100+
logger.setLevel(log_level)
101+
102+
def read_visits():
103+
"""Read visit count from persistent file"""
104+
try:
105+
if VISITS_FILE.exists():
106+
with open(VISITS_FILE, "r") as f:
107+
content = f.read().strip()
108+
if content:
109+
return int(content)
110+
except (ValueError, IOError, OSError) as e:
111+
logger.error(f"Error reading visits file: {e}")
112+
return 0
113+
114+
def write_visits(count):
115+
"""Write visit count to persistent file"""
116+
try:
117+
VISITS_FILE.parent.mkdir(parents=True, exist_ok=True)
118+
119+
temp_file = VISITS_FILE.with_suffix(".tmp")
120+
with open(VISITS_FILE, "w") as f:
121+
f.write(str(count))
122+
temp_file.rename(VISITS_FILE)
123+
124+
logger.debug(f"Visits counter updated to {count}")
125+
126+
except Exception as e:
127+
logger.error(f"Error writing visits file: {e}")
128+
129+
def increment_visits():
130+
"""Increment visit counter and return new value"""
131+
if not APP_CONFIG.get("features", {}).get("visits_tracking", True):
132+
return 0
133+
134+
current = read_visits()
135+
new_count = current + 1
136+
137+
max_visits = APP_CONFIG.get("max_visits_display", 1000000)
138+
if new_count > max_visits:
139+
new_count = max_visits
140+
141+
write_visits(new_count)
142+
143+
visits_total.inc()
144+
145+
return new_count
146+
60147
### Helper functions
61148

62149
def get_uptime():
@@ -82,7 +169,9 @@ def get_system_info():
82169
def index():
83170
uptime_seconds, uptime_human = get_uptime()
84171

85-
logger.info("Main endpoint accessed")
172+
visit_count = increment_visits()
173+
174+
logger.info("Main endpoint accessed, visit #{visit_count}")
86175

87176
response = {
88177
"service": {
@@ -98,28 +187,78 @@ def index():
98187
"current_time": datetime.now(timezone.utc).isoformat(),
99188
"timezone": "UTC"
100189
},
190+
"visits": {
191+
"total": visit_count,
192+
"message": APP_CONFIG.get("welcome_message", "")
193+
},
101194
"request": {
102195
"client_ip": request.remote_addr,
103196
"user_agent": request.headers.get("User-Agent"),
104197
"method": request.method,
105198
"path": request.path
106199
},
107200
"endpoints": [
108-
{"path": "/", "method": "GET", "description": "Service information"},
109-
{"path": "/health", "method": "GET", "description": "Health check"}
201+
{"path": "/", "method": "GET", "description": "Service information with visit counter"},
202+
{"path": "/health", "method": "GET", "description": "Health check"},
203+
{"path": "/metrics", "method": "GET", "description": "Prometheus metrics"},
204+
{"path": "/ready", "method": "GET", "description": "Readiness probe"},
205+
{"path": "/visits", "method": "GET", "description": "Get current visit count"},
206+
{"path": "/config", "method": "GET", "description": "Show current configuration"}
110207
],
111208
}
112209

113210
return jsonify(response)
114211

212+
@app.route("/visits", methods=["GET"])
213+
def get_visits():
214+
"""Endpoint to get current visit count without incrementing"""
215+
visit_count = read_visits()
216+
217+
logger.info(f"Visits endpoint accessed, current count: {visit_count}")
218+
219+
return jsonify({
220+
"total_visits": visit_count,
221+
"data_file": str(VISITS_FILE),
222+
"tracking_enabled": APP_CONFIG.get("features", {}).get("visits_tracking", True)
223+
})
224+
225+
@app.route("/config", methods=["GET"])
226+
def show_config():
227+
"""Endpoint to show current configuration"""
228+
safe_config = {
229+
"app_name": APP_CONFIG.get("app_name"),
230+
"environment": APP_CONFIG.get("environment"),
231+
"features": APP_CONFIG.get("features"),
232+
"log_level": APP_CONFIG.get("log_level"),
233+
"data_dir": DATA_DIR,
234+
"config_file": CONFIG_FILE_PATH
235+
}
236+
237+
logger.info("Config endpoint accessed")
238+
return jsonify(safe_config)
239+
115240
@app.route("/health", methods=["GET"])
116241
def health():
117242
uptime_seconds, _ = get_uptime()
118243

244+
data_writable = False
245+
try:
246+
test_file = Path(DATA_DIR) / ".health_test"
247+
test_file.write_text("test")
248+
test_file.unlink()
249+
data_writable = True
250+
except Exception:
251+
pass
252+
119253
return jsonify({
120254
"status": "healthy",
121255
"timestamp": datetime.now(timezone.utc).isoformat(),
122256
"uptime_seconds": uptime_seconds,
257+
"checks": {
258+
"config_loaded": CONFIG_FILE_PATH if os.path.exists(CONFIG_FILE_PATH) else "default",
259+
"data_writable": data_writable,
260+
"visits_file_exists": VISITS_FILE.exists()
261+
}
123262
})
124263

125264
@app.route("/metrics", methods=["GET"])
@@ -134,6 +273,8 @@ def readiness():
134273
"timestamp": datetime.now(timezone.utc).isoformat(),
135274
"checks": {
136275
"app": "running",
276+
"config": "loaded",
277+
"data_directory": "available" if os.path.exists(DATA_DIR) else "creating",
137278
"dependencies": {
138279
"database": "not_configured",
139280
"cache": "not_configured"
@@ -217,14 +358,22 @@ def log_response(response):
217358

218359
### Entrypoint
219360
if __name__ == "__main__":
361+
Path(DATA_DIR).mkdir(parents=True, exist_ok=True)
362+
220363
logger.info(
221364
"Application started",
222365
extra={
223366
"method": "-",
224367
"path": "-",
225368
"status": "-",
226369
"client_ip": "-",
227-
"port": PORT
370+
"port": PORT,
371+
"config_file": CONFIG_FILE_PATH,
372+
"data_dir": DATA_DIR
228373
}
229374
)
375+
376+
initial_visits = read_visits()
377+
logger.info(f"Initial visits count: {initial_visits}")
378+
230379
app.run(host=HOST, port=PORT, debug=DEBUG)

0 commit comments

Comments
 (0)