Compare commits

6 Commits

Author SHA1 Message Date
Alexey
4acb5ac3bb backedn dev 2025-09-18 14:00:05 +02:00
root
c7e3dbf6b4 dev schema 2025-09-18 12:11:52 +02:00
root
0b30d5ea83 restructure 2025-09-18 12:04:10 +02:00
root
8ac82369f5 read and write with readis 2025-09-18 11:45:51 +02:00
root
1eac2e75ae req file extended 2025-09-18 09:39:36 +02:00
root
847579419b basic creds schema 2025-09-18 09:04:08 +02:00
10 changed files with 174 additions and 5 deletions

9
Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM python:latest
WORKDIR /usr/local/bin
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY src/start.py .
CMD ["src/start.py"]

8
config/inventory.yml Normal file
View File

@@ -0,0 +1,8 @@
- 1:
hostname: '172.16.57.2'
username: 'admin'
password: 'Netapp12'
- 2:
hostname: '172.16.56.2'
username: 'admin'
password: 'Netapp12'

0
initialize.py Normal file
View File

View File

@@ -1,3 +1,6 @@
fastapi[standard]>=0.116.2 fastapi[standard]>=0.116.2
httpx>=0.28.1 httpx>=0.28.1
redis>=6.4.0 redis>=6.4.0
pydantic
redis[hiredis]
dotenv

40
src/database.py Normal file
View File

@@ -0,0 +1,40 @@
import json
import logging
from redis import Redis, ConnectionError
from typing import List
from pydantic import TypeAdapter
from schema import ConfigSchema
def setup_db_conn(redishost, redisport: str):
''' Setup Redis connection and return it open'''
log = logging.getLogger('uvicorn')
try:
redisclient = Redis(host=redishost, port=redisport, decode_responses=True)
if redisclient.ping():
log.info(f"Connected to Redis DB {redishost} on port {redisport}")
else:
log.error(f"Cannot connect to Redis DB {redishost} on port {redisport}")
exit(1)
return redisclient
except ConnectionError as e:
print(f"FATAL: Redis DB {redishost} is unreachable on port {redisport}. Err: {e}")
return None
except Exception as e:
print(f"FATAL: {e}")
return None
def get_inventory_from_redis(redisclient: Redis):
''' Read inventory from Redis '''
cluster_inv = redisclient.hgetall('cluster_inventory')
if 'inventory' in cluster_inv:
return json.loads(cluster_inv['inventory'])
return {}
def get_config_from_db(redisclient: Redis) -> ConfigSchema:
''' Load inventory to global vars'''
GLOBAL_INVENTORY = get_inventory_from_redis(redisclient)
GLOBAL_INVENTORY_VALID = TypeAdapter(List[ConfigSchema]).validate_python(GLOBAL_INVENTORY)
return GLOBAL_INVENTORY_VALID

View File

@@ -4,3 +4,11 @@ from pydantic import BaseModel
class ExampleSchema(BaseModel): class ExampleSchema(BaseModel):
example_field: str example_field: str
another_field: int another_field: int
class ClusterCreds(BaseModel):
"""A structure to hold basic auth cluster credentials for a cluster"""
username: str
password: str
hostname: str = None
cert_filepath: Path = None
key_filepath: Path = None

44
src/initialize.py Normal file
View File

@@ -0,0 +1,44 @@
import os
import json
import logging
import yaml
from pathlib import Path
from dotenv import load_dotenv
from database import setup_db_conn
from schema import ConfigSchema
from typing import List
from pydantic import TypeAdapter
def initialize_config():
load_dotenv()
log = logging.getLogger('uvicorn')
ENV_INVENTORYPATH = os.getenv('cluster_inventory_path')
ENV_REDISHOST = os.getenv('redis_host')
ENV_REDISPORT = os.getenv('redis_port')
log.info(f"Found Cluster Inventory file at: {ENV_INVENTORYPATH}")
if not ENV_INVENTORYPATH or not Path(ENV_INVENTORYPATH).is_file():
print(f"FATAL: Inventory file {ENV_INVENTORYPATH} is missing or not a file.")
return False
try:
with open(ENV_INVENTORYPATH, 'r') as f:
inv = yaml.safe_load(f)
inventory = json.dumps(inv)
except Exception as e:
print(f"FATAL: Cannot read inventory file {ENV_INVENTORYPATH}. Err: {e}")
return False
print(f'[INFO] Importing configuration to DB...')
try:
GLOBAL_INVENTORY_VALID = TypeAdapter(List[ConfigSchema]).validate_python(inv)
redis_conn = setup_db_conn(ENV_REDISHOST, ENV_REDISPORT)
redis_conn.hset('cluster_inventory', mapping={'inventory': inventory})
redis_conn.close()
log.info("Configuration has been loaded.")
return True
except Exception as e:
print(f"FATAL: Redis DB error: {e}")
return False

View File

@@ -1,5 +1,46 @@
def main() -> None: import os
print("Hello, World!") import json
import logging
import yaml
if __name__ == "__main__": from pathlib import Path
main() from dotenv import load_dotenv
from redis import Redis
from contextlib import asynccontextmanager
from pydantic import BaseModel, ValidationError, SecretStr, AnyHttpUrl
from typing import Optional, Literal, List, Union
from fastapi import FastAPI
from database import setup_db_conn, get_inventory_from_redis, get_config_from_db
from src.initialize import initialize_config
from utils import setup_logging
@asynccontextmanager
async def lifespan(app: FastAPI):
''' make loading it async'''
log = logging.getLogger('uvicorn')
cfg_init_result = initialize_config()
shared_redis_conn = setup_db_conn(os.getenv('redis_host'), os.getenv('redis_port'))
if not shared_redis_conn:
log.error("Cannot connect to Redis DB. Exiting...")
exit(1)
inv_check = get_config_from_db(shared_redis_conn)
log.info(f"[DEBUG] Data validity healthcheck (DEVELOPER MODE): {inv_check}")
if not cfg_init_result:
log.error("Configuration initialization failed. Exiting...")
# exit(1)
yield
log.info("Shutting down FastAPI app...")
setup_logging()
log = logging.getLogger('uvicorn')
log.info("Starting FastAPI app...")
app = FastAPI(lifespan=lifespan)

7
src/schema.py Normal file
View File

@@ -0,0 +1,7 @@
from pydantic import BaseModel
class ConfigSchema(BaseModel):
hostname: str
username: str
password: str

9
src/utils.py Normal file
View File

@@ -0,0 +1,9 @@
import logging
def setup_logging() -> None:
"""Configure logging for the application"""
logging.basicConfig(
level=logging.DEBUG,
format="[%(asctime)s] [%(levelname)5s] %(message)s"
)
print(f"Logger is initialized.")