Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuration via environment variables #57

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/qleverfiles-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,27 @@ jobs:
echo -e "\x1b[34mAll checks passed for ${QLEVERFILE}\x1b[0m"
echo
done

- name: Repeat this check using environment variables (derived from the Qleverfile)
working-directory: ${{github.workspace}}/qlever-control
run: |
export QLEVER_ARGCOMPLETE_ENABLED=1
for QLEVERFILE in src/qlever/Qleverfiles/Qleverfile.*; do
echo
echo -e "\x1b[1;34mChecking ${QLEVERFILE} via environment variables\x1b[0m"
echo
NAME=${QLEVERFILE##*.}
rm -f Qleverfile
qlever setup-config $NAME
qlever setup-config --set-envvars-from-qleverfile --file set-envvars.sh
source set-envvars.sh && rm -f Qleverfile
qlever get-data --show
qlever index --show
qlever start --show
qlever ui --show
qlever setup-config --unset-envvars --file unset-envvars.sh
source unset-envvars.sh
echo
echo -e "\x1b[34mAll checks passed for ${QLEVERFILE}\x1b[0m"
echo
done
4 changes: 2 additions & 2 deletions src/qlever/Qleverfiles/Qleverfile.olympics
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Qleverfile for Olympics, use with https://github.com/ad-freiburg/qlever-control
# Qleverfile for Olympics, use with qlever CLI (`pip install qlever`)
#
# qlever get-data # downloads .zip file of size 13 MB, uncompressed to 323 MB
# qlever index # takes ~10 seconds and ~1 GB RAM (on an AMD Ryzen 9 5900X)
Expand All @@ -18,7 +18,7 @@ SETTINGS_JSON = { "ascii-prefixes-only": false, "num-triples-per-batch": 10000

[server]
PORT = 7019
ACCESS_TOKEN = ${data:NAME}_7643543846
ACCESS_TOKEN = ${data:NAME}
MEMORY_FOR_QUERIES = 5G
CACHE_MAX_SIZE = 2G
TIMEOUT = 30s
Expand Down
148 changes: 138 additions & 10 deletions src/qlever/commands/setup_config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from __future__ import annotations

import subprocess
import shlex
from os import environ
from pathlib import Path

import qlever.globals
from qlever.command import QleverCommand
from qlever.envvars import Envvars
from qlever.log import log
from qlever.util import get_random_string
from qlever.util import get_random_string, run_command


class SetupConfigCommand(QleverCommand):
Expand All @@ -32,12 +34,142 @@ def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
def additional_arguments(self, subparser) -> None:
subparser.add_argument(
"config_name",
nargs="?",
type=str,
choices=self.qleverfile_names,
help="The name of the pre-configured Qleverfile to create",
)
subparser.add_argument(
"--set-envvars-from-qleverfile",
action="store_true",
default=False,
help="Show the command line to set the environment variables "
"that correspond to the Qleverfile configuration (suitable for "
"copying and pasting)",
)
subparser.add_argument(
"--unset-envvars",
action="store_true",
default=False,
help="Show the command line to unset all environment variables of "
"the form QLEVER_SECTION_VARIABLE (suitable for copying and pasting)",
)
subparser.add_argument(
"--file",
type=str,
default=None,
help="File to which to write the commands from "
"`--set-envvars-from-qleverfile` or `--unset-envvars`",
)

def execute(self, args) -> bool:
# Either create a Qleverfile or set or unset environment variables.
if args.config_name and args.set_envvars_from_qleverfile:
log.error(
"If you want to set environment variables based on a "
"Qleverfile, first create a Qleverfile by running "
"`qlever setup-config CONFIG_NAME`, and then run "
"`qlever config --set-envvars-from-qleverfile`"
)
return False
if args.config_name and args.unset_envvars:
log.error(
"You cannot create a Qleverfile and unset environment "
"variables at the same time"
)
return False
if args.set_envvars_from_qleverfile and args.unset_envvars:
log.error("You cannot set and unset environment variables at the same time")
return False

# Show the environment variables that correspond to the Qleverfile.
if args.set_envvars_from_qleverfile:
if qlever.globals.qleverfile_path is None:
log.error("No Qleverfile found")
return False
if qlever.globals.qleverfile_config is None:
log.error("Qleverfile found, but contains no configuration")
return False
self.show(
"Set the environment variables that correspond to the "
"configuration from the Qleverfile",
only_show=args.show,
)
if args.show:
return False
else:
set_envvar_cmds = []
for (
section,
varname_and_values,
) in qlever.globals.qleverfile_config.items():
if section == "DEFAULT":
continue
for varname, value in varname_and_values.items():
var = Envvars.envvar_name(section, varname)
set_envvar_cmd = f"export {var}={shlex.quote(value)}"
set_envvar_cmds.append(set_envvar_cmd)
log.info(set_envvar_cmd)
log.info("")
if args.file:
with open(args.file, "w") as f:
for cmd in set_envvar_cmds:
f.write(cmd + "\n")
log.info(
f"Commands written to file `{args.file}`, to set "
"the environment variables, run `source {args.file}` "
"(and if you want to use `qlever` based on these "
"environment variables, move or delete the Qleverfile)"
)
else:
log.info(
"If you want to write these commands to a file, "
"rerun with `--file FILENAME`"
)
return True

# Unset all environment variables of the form QLEVER_SECTION_VARIABLE.
# Note that this cannot be done in this script because it would not
# affect the shell calling this script. Instead, show the commands for
# unsetting the environment variables to copy and paste.
if args.unset_envvars:
self.show(
"Unset all environment variables of the form "
"QLEVER_SECTION_VARIABLE (for copying and pasting, "
"this command cannot affect the shell from which you "
" are calling it)",
only_show=args.show,
)
if args.show:
return False
if qlever.globals.envvars_config is None:
log.info("No environment variables found")
else:
envvar_names = []
for (
section,
varname_and_values,
) in qlever.globals.envvars_config.items():
for varname, value in varname_and_values.items():
envvar_name = Envvars.envvar_name(section, varname)
envvar_names.append(envvar_name)
unset_cmd = f"unset {' '.join(envvar_names)}"
log.info(unset_cmd)
log.info("")
if args.file:
with open(args.file, "w") as f:
f.write(unset_cmd)
log.info(
f"Command written to file `{args.file}`, to unset "
"the environment variables, run `source {args.file}`"
)
else:
log.info(
"If you want to write this command to a file, "
"rerun with `--file FILENAME`"
)
return True

# Show a warning if `QLEVER_OVERRIDE_SYSTEM_NATIVE` is set.
qlever_is_running_in_container = environ.get("QLEVER_IS_RUNNING_IN_CONTAINER")
if qlever_is_running_in_container:
Expand All @@ -47,11 +179,13 @@ def execute(self, args) -> bool:
"(since inside the container, QLever should run natively)"
)
log.info("")

# Construct the command line and show it.
qleverfile_path = self.qleverfiles_path / f"Qleverfile.{args.config_name}"
random_string = get_random_string(12)
setup_config_cmd = (
f"cat {qleverfile_path}"
f" | sed -E 's/(^ACCESS_TOKEN.*)/\\1_{get_random_string(12)}/'"
f" | sed -E 's/(^ACCESS_TOKEN.*)/\\1_{random_string}/'"
)
if qlever_is_running_in_container:
setup_config_cmd += (
Expand All @@ -76,13 +210,7 @@ def execute(self, args) -> bool:

# Copy the Qleverfile to the current directory.
try:
subprocess.run(
setup_config_cmd,
shell=True,
check=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
)
run_command(setup_config_cmd)
except Exception as e:
log.error(
f'Could not copy "{qleverfile_path}"' f" to current directory: {e}"
Expand Down
98 changes: 98 additions & 0 deletions src/qlever/commands/show_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from __future__ import annotations

import shlex
from pathlib import Path

import qlever.globals
from qlever.command import QleverCommand
from qlever.envvars import Envvars
from qlever.log import log


class ShowConfigCommand(QleverCommand):
"""
Class for showing the current configuration (either via a Qleverfile or
via environment variables).

"""

def __init__(self):
self.qleverfiles_path = Path(__file__).parent.parent / "Qleverfiles"
self.qleverfile_names = [
p.name.split(".")[1] for p in self.qleverfiles_path.glob("Qleverfile.*")
]

def description(self) -> str:
return "Set up a Qleverfile or show the current configuration"

def should_have_qleverfile(self) -> bool:
return False

def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
return {}

def additional_arguments(self, subparser) -> None:
subparser.add_argument(
"--varname-width",
type=int,
default=25,
help="Width for variable names in the output",
)

def execute(self, args) -> bool:
# Determine if there is a Qleverfile or environment variables (we need
# one or the other, but not both).
qleverfile_exists = qlever.globals.qleverfile_path is not None
envvars_exist = qlever.globals.envvars_config is not None
if qleverfile_exists and envvars_exist:
log.error(
"There are both a Qleverfile and environment variables, "
"this should not happen because it is bound to cause "
"confusion; either remove the Qleverfile or unset the "
"environment variables using `qlever config --unset-envvars`"
)
return False
if not qleverfile_exists and not envvars_exist:
log.error("Neither a Qleverfile nor environment variables found")
return False

# Show the configuration from the Qleverfile.
if qleverfile_exists:
if qlever.globals.qleverfile_config is None:
log.error("Qleverfile found, but contains no configuration")
return False
self.show(
f"Show the configuration from "
f"{qlever.globals.qleverfile_path} (with any variables "
f"on the right-hand side already substituted)",
only_show=args.show,
)
if args.show:
return True

is_first_section = True
for section, varname_and_values in qlever.globals.qleverfile_config.items():
if section == "DEFAULT":
continue
if not is_first_section:
log.info("")
is_first_section = False
log.info(f"[{section}]")
for varname, value in varname_and_values.items():
log.info(f"{varname.upper():{args.varname_width}} = " f"{value}")
return True

# Show all environment variables of the form QLEVER_SECTION_VARIABLE.
if envvars_exist:
self.show(
"Show all environment variables of the form QLEVER_SECTION_VARIABLE",
only_show=args.show,
)
if args.show:
return True

for section, varname_and_values in qlever.globals.envvars_config.items():
for varname, value in varname_and_values.items():
var = Envvars.envvar_name(section, varname)
log.info(f"{var:{args.varname_width+7}}" f" = {shlex.quote(value)}")
return True
Loading