44import logging
55import sys
66import time
7+ import json
8+ from pathlib import Path
79from datetime import datetime , timezone
810from pythonjsonlogger import jsonlogger
911
2123SERVICE_DESCRIPTION = "DevOps course info service"
2224FRAMEWORK = "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
2531logger = logging .getLogger ()
2632logger .setLevel (logging .INFO )
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
62149def get_uptime ():
@@ -82,7 +169,9 @@ def get_system_info():
82169def 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" ])
116241def 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
219360if __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