Skip to content
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
10 changes: 8 additions & 2 deletions src/qlever/commands/add_text_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
"text_words_file",
"text_docs_file",
],
"runtime": ["system", "image", "index_container"],
"runtime": [
"system",
"image",
"index_container",
"disable_selinux",
],
}

def additional_arguments(self, subparser) -> None:
Expand All @@ -54,7 +59,7 @@ def execute(self, args) -> bool:
"from_text_records_and_literals",
]:
add_text_index_cmd += (
f" -w {args.text_words_file}" f" -d {args.text_docs_file}"
f" -w {args.text_words_file} -d {args.text_docs_file}"
)
if args.text_index in [
"from_literals",
Expand All @@ -73,6 +78,7 @@ def execute(self, args) -> bool:
args.index_container,
volumes=[("$(pwd)", "/index")],
working_directory="/index",
disable_selinux=args.disable_selinux == "yes",
)

# Show the command line.
Expand Down
8 changes: 7 additions & 1 deletion src/qlever/commands/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
"stxxl_memory",
"parser_buffer_size",
],
"runtime": ["system", "image", "index_container"],
"runtime": [
"system",
"image",
"index_container",
"disable_selinux",
],
}

def additional_arguments(self, subparser) -> None:
Expand Down Expand Up @@ -266,6 +271,7 @@ def execute(self, args) -> bool:
args.index_container,
volumes=[("$(pwd)", "/index")],
working_directory="/index",
disable_selinux=args.disable_selinux == "yes",
)

# Command for writing the settings JSON to a file.
Expand Down
17 changes: 9 additions & 8 deletions src/qlever/commands/start.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

import subprocess
import time
from pathlib import Path

Expand Down Expand Up @@ -78,14 +77,15 @@ def wrap_command_in_container(args, start_cmd) -> str:
volumes=[("$(pwd)", "/index")],
ports=[(args.port, args.port)],
working_directory="/index",
disable_selinux=args.disable_selinux == "yes",
)
return start_cmd


# Set the index description.
def set_index_description(access_arg, port, desc) -> bool:
def set_index_description(access_arg, endpoint_url, desc) -> bool:
curl_cmd = (
f"curl -Gs http://localhost:{port}/api"
f"curl -Gs {endpoint_url}/api"
f' --data-urlencode "index-description={desc}"'
f" {access_arg} > /dev/null"
)
Expand All @@ -99,9 +99,9 @@ def set_index_description(access_arg, port, desc) -> bool:


# Set the text description.
def set_text_description(access_arg, port, text_desc) -> bool:
def set_text_description(access_arg, endpoint_url, text_desc) -> bool:
curl_cmd = (
f"curl -Gs http://localhost:{port}/api"
f"curl -Gs {endpoint_url}/api"
f' --data-urlencode "text-description={text_desc}"'
f" {access_arg} > /dev/null"
)
Expand Down Expand Up @@ -151,7 +151,8 @@ def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
"use_text_index",
"warmup_cmd",
],
"runtime": ["system", "image", "server_container"],
"runtime": ["system", "image", "server_container",
"disable_selinux"],
}

def additional_arguments(self, subparser) -> None:
Expand Down Expand Up @@ -304,13 +305,13 @@ def execute(self, args) -> bool:
access_arg = f'--data-urlencode "access-token={args.access_token}"'
if args.description:
ret = set_index_description(
access_arg, args.port, args.description
access_arg, args.endpoint_url, args.description
)
if not ret:
return False
if args.text_description:
ret = set_text_description(
access_arg, args.port, args.text_description
access_arg, args.endpoint_url, args.text_description
)
if not ret:
return False
Expand Down
9 changes: 8 additions & 1 deletion src/qlever/commands/system_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,14 @@ def should_have_qleverfile(self) -> bool:
return True

def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
return {"runtime": ["system", "image", "server_container"]}
return {
"runtime": [
"system",
"image",
"server_container",
"disable_selinux",
]
}

def additional_arguments(self, subparser) -> None:
pass
Expand Down
2 changes: 2 additions & 0 deletions src/qlever/commands/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
return {
"data": ["name"],
"server": ["host_name", "port"],
"runtime": ["disable_selinux"],
"ui": [
"ui_port",
"ui_config",
Expand Down Expand Up @@ -130,6 +131,7 @@ def execute(self, args) -> bool:
start_ui_cmd = (
f"{args.ui_system} run -d "
f"--volume $(pwd):/app/db "
f"{'--security-opt label=disable ' if args.disable_selinux == 'yes' else ''}"
f"--env QLEVERUI_DATABASE_URL=sqlite:////app/db/{ui_db_file} "
f"--publish {args.ui_port}:7000 "
f"--name {args.ui_container} "
Expand Down
44 changes: 44 additions & 0 deletions src/qlever/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import argparse
import os
import socket
import traceback
from importlib.metadata import version
from pathlib import Path
Expand All @@ -10,8 +11,10 @@
from termcolor import colored

from qlever import command_objects, engine_name, script_name
from qlever.containerize import Containerize
from qlever.log import log, log_levels
from qlever.qleverfile import Qleverfile
from qlever.util import selinux_enforcing


# Simple exception class for configuration errors (the class need not do
Expand Down Expand Up @@ -226,6 +229,47 @@ def add_qleverfile_option(parser):
"arguments on the command line. This is possible, "
"but not recommended.")

# Warn if the host name resolves to IPv6 first and the system is
# a container runtime. Container port forwarding (podman/docker in
# rootless mode) typically only forwards on IPv4, so curl will
# connect via IPv6 and fail.
host_name = getattr(args, "host_name", None)
system = getattr(args, "ui_system", None) or getattr(
args, "system", "native"
)
ipv6_warning = False
if host_name and system in Containerize.supported_systems():
try:
addrinfo = socket.getaddrinfo(host_name, None)
if addrinfo and addrinfo[0][0] == socket.AF_INET6:
log.warning(
f"Your system resolves '{host_name}' to an "
"IPv6 address first, which may cause connection "
"failures with containerized servers. If you face "
"connection issues, consider using an explicit "
"IPv4 address like 127.0.0.1 (via HOST_NAME in "
"your Qleverfile or --host-name on the command line)"
)
ipv6_warning = True
except Exception:
pass

# Warn if SELinux is enforcing but not disabled for the container.
disable_selinux = getattr(args, "disable_selinux", None)
if (
system in Containerize.supported_systems()
and disable_selinux == "no"
and selinux_enforcing()
):
if ipv6_warning:
log.info("")
log.warning(
"SELinux is enforcing, which may cause permission "
"errors with bind-mounted files. If you experience "
"issues, set DISABLE_SELINUX = yes in your "
"Qleverfile or use --disable-selinux yes"
)

# Warn if the old binary names are still being used.
if "IndexBuilderMain" in getattr(args, "index_binary", ""):
log.warning("The index binary has been renamed from "
Expand Down
10 changes: 10 additions & 0 deletions src/qlever/containerize.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def containerize_command(
ports: list[tuple[int, int]] = [],
working_directory: Optional[str] = None,
use_bash: bool = True,
disable_selinux: bool = False,
) -> str:
"""
Get the command to run `cmd` with the given `container_system` and the
Expand Down Expand Up @@ -77,11 +78,18 @@ def containerize_command(
f" -w {working_directory}" if working_directory is not None else ""
)

# If SELinux is disabled for the container, add the security option
# so that the container can access bind-mounted host files.
selinux_option = (
" --security-opt label=disable" if disable_selinux else ""
)

# Construct the command that runs `cmd` with the given container
# system.
containerized_cmd = (
f"{container_system} {run_subcommand}"
f"{user_option}"
f"{selinux_option}"
f" -v /etc/localtime:/etc/localtime:ro"
f"{volume_options}"
f"{port_options}"
Expand Down Expand Up @@ -155,6 +163,7 @@ def run_in_container(cmd: str, args) -> Optional[str]:
if args.system in Containerize.supported_systems():
if not args.server_container:
args.server_container = get_random_string(20)
disable_selinux = getattr(args, "disable_selinux", "no") == "yes"
run_cmd = Containerize().containerize_command(
cmd,
args.system,
Expand All @@ -163,5 +172,6 @@ def run_in_container(cmd: str, args) -> Optional[str]:
args.server_container,
volumes=[("$(pwd)", "/index")],
working_directory="/index",
disable_selinux=disable_selinux,
)
return run_command(run_cmd, return_output=True)
12 changes: 11 additions & 1 deletion src/qlever/qleverfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,16 @@ def arg(*args, **kwargs):
type=str,
help=f"The name of the container used by `{script_name} start`",
)
runtime_args["disable_selinux"] = arg(
"--disable-selinux",
choices=["yes", "no"],
default="no",
help=(
"Disable SELinux confinement for the container, "
"use this if you get permission errors on "
"bind-mounted files (e.g. on Fedora or RHEL)"
),
)

ui_args["ui_port"] = arg(
"--ui-port",
Expand Down Expand Up @@ -511,7 +521,7 @@ def read(qleverfile_path):
log.warning(
"Could not get the hostname, using `localhost` as default"
)
pass
config["server"]["host_name"] = "localhost"

# Return the parsed Qleverfile with the added inherited values.
return config
Expand Down
11 changes: 11 additions & 0 deletions src/qlever/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ def binary_exists(binary: str, cmd_arg: str, args) -> bool:

is_containerized = args.system in Containerize.supported_systems()
cmd = f"{binary} --help"
disable_selinux = getattr(args, "disable_selinux", "no") == "yes"
if is_containerized and script_name == "qlever":
cmd = Containerize().containerize_command(
cmd,
Expand All @@ -345,6 +346,7 @@ def binary_exists(binary: str, cmd_arg: str, args) -> bool:
"qlever.check-binary",
volumes=[("$(pwd)", "/index")],
working_directory="/index",
disable_selinux=disable_selinux,
)

try:
Expand Down Expand Up @@ -400,6 +402,15 @@ def input_files_exist(input_files: str) -> bool:
return True


def selinux_enforcing() -> bool:
"""Check if SELinux is in enforcing mode by reading the kernel interface."""
try:
with open("/sys/fs/selinux/enforce") as f:
return f.read().strip() == "1"
except (FileNotFoundError, PermissionError):
return False


def build_image(build_cmd: str, system: str, image: str) -> bool:
"""
Build a container image using the build command, container system and
Expand Down
7 changes: 6 additions & 1 deletion test/qlever/commands/test_index_other_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ def test_relevant_qleverfile_arguments(self):
"stxxl_memory",
"parser_buffer_size",
],
"runtime": ["system", "image", "index_container"],
"runtime": [
"system",
"image",
"index_container",
"disable_selinux",
],
},
)

Expand Down
Loading
Loading