Source code for flux_restful.core.config
import logging
import os
import re
import secrets
import shlex
import string
from typing import List, Optional
from pydantic_settings import BaseSettings
logger = logging.getLogger(__name__)
TRUE_VALUES = ("1", "true", "yes", "on")
# Authentication backends known to the server (implemented in app/auth)
KNOWN_AUTH_BACKENDS = ["none", "database", "shared-secret", "pam", "oidc"]
[docs]def get_int_envar(key, default=None):
"""
Get (and parse) an integer environment variable
"""
value = os.environ.get(key)
if not value:
value = default
try:
value = int(value)
return value
except Exception:
return default
[docs]def get_bool_envar(key, default=False):
"""
Get a boolean environment variable. Accepts 1/0, true/false, yes/no, on/off.
An unset or empty variable returns the default.
"""
value = os.environ.get(key)
if value is None or not value.strip():
return default
return value.strip().lower() in TRUE_VALUES
[docs]def get_list_envar(key) -> List[str]:
"""
Get a comma separated list from the environment.
"""
value = os.environ.get(key) or ""
return [item.strip() for item in value.split(",") if item.strip()]
[docs]def get_auth_backend() -> str:
"""
Determine the auth backend from FLUX_AUTH_BACKEND.
FLUX_REQUIRE_AUTH predates FLUX_AUTH_BACKEND: it meant database users plus
the shared-secret token handshake, so it maps to "shared-secret".
"""
backend = (os.environ.get("FLUX_AUTH_BACKEND") or "").strip().lower()
if not backend:
backend = "shared-secret" if get_bool_envar("FLUX_REQUIRE_AUTH") else "none"
if backend not in KNOWN_AUTH_BACKENDS:
raise ValueError(
f"FLUX_AUTH_BACKEND must be one of {', '.join(KNOWN_AUTH_BACKENDS)}, "
f"got '{backend}'"
)
return backend
[docs]def get_option_flags(key, prefix="-o"):
"""
Wrapper around parse_option_flags to get from environment.
The function can then be shared to parse flags from the UI
in the same way.
"""
flags = os.environ.get(key) or {}
if not flags:
return flags
return parse_option_flags(flags, prefix)
[docs]def parse_option_flags(flags, prefix="-o"):
"""
Parse key value pairs (optionally with a prefix) from the environment.
"""
values = {}
for flag in shlex.split(flags):
if "=" not in flag:
logger.warning(f"Missing '=' in flag {flag}, cannot parse.")
continue
option, value = flag.split("=", 1)
if option.startswith(prefix):
option = re.sub(f"^{prefix}", "", option)
values[option] = value
return values
[docs]def generate_secret_key(length=32):
"""
Generate a random key, if one is not provided.
"""
alphabet = string.ascii_letters + string.digits
return "".join(secrets.choice(alphabet) for i in range(length))
[docs]class Settings(BaseSettings):
"""
Basic settings and defaults for the Flux RESTFul API
"""
app_name: str = "Flux RESTFul API"
api_version: str = "v1"
# These map to envars, e.g., FLUX_USER
has_gpus: bool = get_bool_envar("FLUX_HAS_GPUS")
# Assume there is at least one node!
flux_nodes: int = get_int_envar("FLUX_NUMBER_NODES", 1)
# Authentication. See app/auth for the backends.
auth_backend: str = get_auth_backend()
require_auth: bool = auth_backend != "none"
# Usernames that are superusers regardless of backend (comma separated)
admin_users: List[str] = get_list_envar("FLUX_ADMIN_USERS")
# Shared with clients so they can encode the /v1/token handshake payload.
# It is never used to sign access tokens.
secret_key: Optional[str] = os.environ.get("FLUX_SECRET_KEY")
# Server-only key that signs access tokens. Required by every backend that
# issues tokens, so that all workers sign with the same key (see
# entrypoint.sh, which generates one per container start if unset).
token_signing_key: Optional[str] = os.environ.get("FLUX_TOKEN_SIGNING_KEY")
# Lowest uid that may be mapped to a system account by a backend (pam, or
# oidc in multi-user mode). Keeps root and daemon accounts out of reach
# of a bad claim mapping.
min_uid: int = get_int_envar("FLUX_MIN_UID", 1000)
# pam backend
pam_service: str = os.environ.get("FLUX_PAM_SERVICE") or "login"
# oidc backend
oidc_issuer: Optional[str] = os.environ.get("FLUX_OIDC_ISSUER")
oidc_audience: Optional[str] = os.environ.get("FLUX_OIDC_AUDIENCE")
oidc_jwks_url: Optional[str] = os.environ.get("FLUX_OIDC_JWKS_URL")
# Claim used as the username. Defaults to "sub", the only claim OIDC
# guarantees to be stable and unique for an issuer.
oidc_username_claim: Optional[str] = os.environ.get("FLUX_OIDC_USERNAME_CLAIM")
# If you change this, also change in alembic.ini
db_file: str = "sqlite:///./flux-restful.db"
flux_user: str = os.environ.get("FLUX_USER") or "fluxuser"
flux_token: Optional[str] = os.environ.get("FLUX_TOKEN")
flux_server_mode: Optional[str] = (
os.environ.get("FLUX_SERVER_MODE") or "single-user"
)
# Validate the server mode provided.
if flux_server_mode not in ["single-user", "multi-user"]:
raise ValueError("FLUX_SERVER_MODE must be single-user or multi-user")
# Expires in 10 hours
access_token_expires_minutes: int = get_int_envar(
"FLUX_ACCESS_TOKEN_EXPIRES_MINUTES", 600
)
# Default server option flags
option_flags: dict = get_option_flags("FLUX_OPTION_FLAGS")
# If the user requests a launcher, be strict.
# We only allow nextflow and snakemake, sorry
known_launchers: list = ["nextflow", "snakemake"]
settings = Settings()