Update config loading/validation

This commit is contained in:
Parker M. 2024-11-03 21:14:42 -06:00
parent db1857a18f
commit 9e99695611
Signed by: parker
GPG Key ID: 505ED36FC12B5D5E
7 changed files with 106 additions and 203 deletions

2
.gitignore vendored
View File

@ -3,6 +3,6 @@ data.db
__pycache__
.DS_Store
internal_notes.txt
config.ini
config.yaml
data
docker-volume

View File

@ -15,7 +15,7 @@ import random
from models import User, Link
from database import *
from app.util.log import log
from var import BASE_URL
from config import BASE_URL
class FlaskUser(UserMixin):

View File

@ -4,7 +4,7 @@ from ua_parser import user_agent_parser
from ip2locationio.ipgeolocation import IP2LocationIOAPIError
from database import SessionLocal
from var import LOG, API_KEY, IP_TO_LOCATION
from config import LOG, API_KEY, IP_TO_LOCATION
from models import Link, Record
configuration = ip2locationio.Configuration(API_KEY)

101
config.py Normal file
View File

@ -0,0 +1,101 @@
import jsonschema
import os
import yaml
import validators
import sys
import logging
from colorlog import ColoredFormatter
log_level = logging.DEBUG
log_format = (
" %(log_color)s%(levelname)-8s%(reset)s |"
" %(log_color)s%(message)s%(reset)s"
)
logging.root.setLevel(log_level)
formatter = ColoredFormatter(log_format)
stream = logging.StreamHandler()
stream.setLevel(log_level)
stream.setFormatter(formatter)
LOG = logging.getLogger("pythonConfig")
LOG.setLevel(log_level)
LOG.addHandler(stream)
BASE_URL = None
IP_TO_LOCATION = None
API_KEY = None
schema = {
"type": "object",
"properties": {
"config": {
"type": "object",
"properties": {
"base_url": {"type": "string"},
"ip_to_location": {"type": "boolean"},
"api_key": {"type": "string"},
},
"required": ["base_url", "ip_to_location"],
}
},
"required": ["config"],
}
# Load config file or create new template
def load_config():
if os.path.exists("/.dockerenv"):
file_path = "/data/config.yaml"
else:
file_path = "config.yaml"
try:
with open(file_path, "r") as f:
file_contents = f.read()
validate_config(file_contents)
except FileNotFoundError:
# Create new config.yaml w/ template
with open(file_path, "w") as f:
f.write(
"""
base_url: ""
ip_to_location: ""
api_key: ""
"""
)
LOG.critical(
"`config.yaml` was not found, a template has been created."
" Please fill out the necessary information and restart."
)
sys.exit()
# Validate the options within config.yaml
def validate_config(file_contents):
global BASE_URL, IP_TO_LOCATION, API_KEY
config = yaml.safe_load(file_contents)
try:
jsonschema.validate(config, schema)
except jsonschema.ValidationError as e:
LOG.error(e.message)
sys.exit()
# Validate BASE_URL
if not validators.url(config["config"]["base_url"]):
LOG.error("BASE_URL is not a valid URL")
else:
BASE_URL = config["config"]["base_url"]
# Make IP_TO_LOCATION a boolean
IP_TO_LOCATION = bool(config["config"]["ip_to_location"])
# Validate API_KEY if IP_TO_LOCATION is set to TRUE
if IP_TO_LOCATION:
if not config["config"]["api_key"]:
LOG.error("API_KEY is not set")
else:
API_KEY = config["config"]["api_key"]

View File

@ -1,7 +1,7 @@
from werkzeug.middleware.dispatcher import DispatcherMiddleware
from a2wsgi import ASGIMiddleware
from validate_config import validate_config
from config import load_config
from app.main import app as flask_app
from api.main import app as fastapi_app
from database import Base, engine
@ -17,5 +17,5 @@ flask_app.wsgi_app = DispatcherMiddleware(
)
if __name__ == "__main__":
validate_config()
load_config()
flask_app.run(port=5252)

View File

@ -1,139 +0,0 @@
import configparser
import validators
import os
import sys
from var import LOG
"""
Validate the config of a Docker run (environment variables)
"""
def validate_docker_config():
errors = 0
# Validate BASE_URL
try:
if not os.environ["BASE_URL"]:
LOG.error("BASE_URL is not set")
errors += 1
elif not validators.url(os.environ["BASE_URL"]):
LOG.error("BASE_URL is not a valid URL")
errors += 1
except KeyError:
LOG.critical("BASE_URL does not exist!")
errors += 1
# Validate IP_TO_LOCATION
try:
if not os.environ["IP_TO_LOCATION"]:
LOG.error("IP_TO_LOCATION is not set")
errors += 1
elif os.environ["IP_TO_LOCATION"].upper() not in ["TRUE", "FALSE", "T", "F"]:
LOG.error("IP_TO_LOCATION is not set to TRUE or FALSE")
errors += 1
else:
iptolocation = (
True if os.environ["IP_TO_LOCATION"].upper() in ["TRUE", "T"] else False
)
# Validate API_KEY if IP_TO_LOCATION is set to TRUE
if iptolocation:
try:
if not os.environ["API_KEY"]:
LOG.error("API_KEY is not set")
errors += 1
except KeyError:
LOG.critical("API_KEY does not exist!")
errors += 1
except KeyError:
LOG.critical("IP_TO_LOCATION does not exist!")
errors += 1
if errors > 0:
LOG.critical(f"{errors} error(s) found in environment variables")
sys.exit()
"""
Validate the config of a bare metal run (config.ini file)
"""
def validate_bare_metal_config(file_contents):
config = configparser.ConfigParser()
config.read_string(file_contents)
errors = 0
# Validate BASE_URL
try:
if not config["CONFIG"]["BASE_URL"]:
LOG.error("BASE_URL is not set")
errors += 1
elif not validators.url(config["CONFIG"]["BASE_URL"]):
LOG.error("BASE_URL is not a valid URL")
errors += 1
except ValueError:
LOG.critical("BASE_URL does not exist!")
errors += 1
# Validate IP_TO_LOCATION
try:
if not config["CONFIG"]["IP_TO_LOCATION"]:
LOG.error("IP_TO_LOCATION is not set")
errors += 1
elif config["CONFIG"]["IP_TO_LOCATION"].upper() not in [
"TRUE",
"FALSE",
"T",
"F",
]:
LOG.error("IP_TO_LOCATION is not set to TRUE or FALSE")
errors += 1
else:
iptolocation = (
True
if config["CONFIG"]["IP_TO_LOCATION"].upper() in ["TRUE", "T"]
else False
)
# Validate API_KEY if IP_TO_LOCATION is set to TRUE
if iptolocation:
try:
if not config["CONFIG"]["API_KEY"]:
LOG.error("API_KEY is not set")
errors += 1
except ValueError:
LOG.critical("API_KEY does not exist!")
errors += 1
except ValueError:
LOG.critical("IP_TO_LOCATION does not exist!")
errors += 1
if errors > 0:
LOG.critical(f"{errors} error(s) found in `config.ini`")
sys.exit()
def validate_config():
# If the app is running in Docker
if "BASE_URL" in os.environ or "IP_TO_LOCATION" in os.environ:
return validate_docker_config()
# Otherwise, the app is running on bare metal
try:
with open("config.ini", "r") as f:
file_contents = f.read()
return validate_bare_metal_config(file_contents)
except FileNotFoundError:
config = configparser.ConfigParser()
config["CONFIG"] = {"BASE_URL": "", "IP_TO_LOCATION": "", "API_KEY": ""}
with open("config.ini", "w") as configfile:
config.write(configfile)
LOG.error(
"`config.ini` has been created. Fill out the necessary information then re-run."
)
sys.exit()

59
var.py
View File

@ -1,59 +0,0 @@
import configparser
import logging
import os
from colorlog import ColoredFormatter
log_level = logging.DEBUG
log_format = "%(log_color)s%(levelname)-8s%(reset)s %(log_color)s%(message)s%(reset)s"
logging.root.setLevel(log_level)
formatter = ColoredFormatter(log_format)
stream = logging.StreamHandler()
stream.setLevel(log_level)
stream.setFormatter(formatter)
LOG = logging.getLogger("pythonConfig")
LOG.setLevel(log_level)
LOG.addHandler(stream)
# If the app is running in Docker
if "BASE_URL" in os.environ or "IP_TO_LOCATION" in os.environ:
BASE_URL = os.environ["BASE_URL"]
IP_TO_LOCATION = (
True if os.environ["IP_TO_LOCATION"].upper() in ["TRUE", "T"] else False
)
if IP_TO_LOCATION:
API_KEY = os.environ["API_KEY"]
else:
API_KEY = None
# Otherwise, the app is running on bare metal
try:
with open("config.ini", "r") as f:
config = configparser.ConfigParser()
config.read_string(f.read())
BASE_URL = config["CONFIG"]["BASE_URL"]
IP_TO_LOCATION = (
True
if config["CONFIG"]["IP_TO_LOCATION"].upper() in ["TRUE", "T"]
else False
)
if IP_TO_LOCATION:
API_KEY = config["CONFIG"]["API_KEY"]
else:
API_KEY = None
except FileNotFoundError:
config = configparser.ConfigParser()
config["CONFIG"] = {"BASE_URL": "", "IP_TO_LOCATION": "", "API_KEY": ""}
with open("config.ini", "w") as configfile:
config.write(configfile)
LOG.error(
"`config.ini` has been created. Fill out the necessary information then re-run."
)
exit()