* bump lib

* rest

* bump

* bump app version

* ddns-updater fiox

* fix

* fix
This commit is contained in:
Stavros Kois
2025-11-28 18:14:53 +02:00
committed by GitHub
parent cc28687ce5
commit c0382b4e39
24468 changed files with 564490 additions and 544481 deletions

View File

@@ -14,8 +14,8 @@ icon: https://media.sys.truenas.net/apps/actual-budget/icons/icon.png
keywords:
- finance
- budget
lib_version: 2.1.64
lib_version_hash: 920ca771628a09b7f9a11cd950795d5fcfc7a4e3a3f90e3da49c6ffe3f6c6e1e
lib_version: 2.1.65
lib_version_hash: f92a9ee78c78fc77f86e7d8b545bd4c605c31c599e2c5da59f1615aa516cb8b5
maintainers:
- email: dev@truenas.com
name: truenas
@@ -36,4 +36,4 @@ sources:
- https://hub.docker.com/r/actualbudget/actual-server
title: Actual Budget
train: community
version: 1.3.20
version: 1.3.21

View File

@@ -4,7 +4,7 @@ images:
tag: 25.11.0
container_utils_image:
repository: ixsystems/container-utils
tag: 1.0.1
tag: 1.0.2
consts:
actual_budget_container_name: actual-budget
perms_container_name: permissions

View File

@@ -1,95 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class ElasticConfig(TypedDict):
password: str
node_name: str
port: NotRequired[int]
volume: "IxStorage"
class ElasticSearchContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: ElasticConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/usr/share/elasticsearch/data"
for key in ("password", "node_name", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for ElasticSearch")
c = self._render_instance.add_container(name, image)
c.set_user(1000, 1000)
basic_auth_header = self._render_instance.funcs["basic_auth_header"]("elastic", config["password"])
c.healthcheck.set_test(
"curl",
{
"port": self.get_port(),
"path": "/_cluster/health?local=true",
"headers": [("Authorization", basic_auth_header)],
},
)
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("ELASTIC_PASSWORD", config["password"])
c.environment.add_env("http.port", self.get_port())
c.environment.add_env("path.data", self._data_dir)
c.environment.add_env("path.repo", self.get_snapshots_dir())
c.environment.add_env("node.name", config["node_name"])
c.environment.add_env("discovery.type", "single-node")
c.environment.add_env("xpack.security.enabled", True)
c.environment.add_env("xpack.security.transport.ssl.enabled", False)
perms_instance.add_or_skip_action(
f"{self._name}_elastic_data", config["volume"], {"uid": 1000, "gid": 1000, "mode": "check"}
)
self._get_repo(image, ("docker.elastic.co/elasticsearch/elasticsearch"))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(
f"Unsupported repo [{repo}] for elastic search. Supported repos: {', '.join(supported_repos)}"
)
return repo
def get_port(self):
return self._config.get("port") or 9200
def get_url(self):
return f"http://{self._name}:{self.get_port()}"
def get_snapshots_dir(self):
return f"{self._data_dir}/snapshots"

View File

@@ -1,91 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise
class MariadbConfig(TypedDict):
user: str
password: str
database: str
root_password: NotRequired[str]
port: NotRequired[int]
auto_upgrade: NotRequired[bool]
volume: "IxStorage"
class MariadbContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MariadbConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for mariadb")
port = valid_port_or_raise(self.get_port())
root_password = config.get("root_password") or config["password"]
auto_upgrade = config.get("auto_upgrade", True)
self._get_repo(image, ("mariadb"))
c = self._render_instance.add_container(name, image)
c.set_user(999, 999)
c.healthcheck.set_test("mariadb", {"password": root_password})
c.remove_devices()
c.add_storage("/var/lib/mysql", config["volume"])
perms_instance.add_or_skip_action(
f"{self._name}_mariadb_data", config["volume"], {"uid": 999, "gid": 999, "mode": "check"}
)
c.environment.add_env("MARIADB_USER", config["user"])
c.environment.add_env("MARIADB_PASSWORD", config["password"])
c.environment.add_env("MARIADB_ROOT_PASSWORD", root_password)
c.environment.add_env("MARIADB_DATABASE", config["database"])
c.environment.add_env("MARIADB_AUTO_UPGRADE", str(auto_upgrade).lower())
c.set_command(["--port", str(port)])
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for mariadb. Supported repos: {', '.join(supported_repos)}")
return repo
def get_url(self, variant: str):
addr = f"{self._name}:{self.get_port()}"
urls = {
"jdbc": f"jdbc:mariadb://{addr}/{self._config['database']}",
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]
def get_port(self):
return self._config.get("port") or 3306
@property
def container(self):
return self._container

View File

@@ -1,85 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class MeiliConfig(TypedDict):
master_key: str
port: NotRequired[int]
volume: "IxStorage"
class MeilisearchContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MeiliConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/meili_data"
for key in ("master_key", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for meilisearch")
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("curl", {"port": self.get_port(), "path": "/health"})
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("MEILI_HTTP_ADDR", f"0.0.0.0:{self.get_port()}")
c.environment.add_env("MEILI_NO_ANALYTICS", True)
c.environment.add_env("MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE", True)
c.environment.add_env("MEILI_MASTER_KEY", config["master_key"])
perms_instance.add_or_skip_action(
f"{self._name}_meili_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
self._get_repo(image, ("getmeili/meilisearch",))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(
f"Unsupported repo [{repo}] for meilisearch. Supported repos: {', '.join(supported_repos)}"
)
return repo
def get_port(self):
return self._config.get("port") or 7700
def get_url(self):
return f"http://{self._name}:{self.get_port()}"

View File

@@ -1,97 +0,0 @@
import urllib.parse
from typing import TYPE_CHECKING, TypedDict
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class MongoDBConfig(TypedDict):
user: str
password: str
database: str
volume: "IxStorage"
class MongoDBContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MongoDBConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/data/db"
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for mongodb")
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("mongodb", {"db": config["database"]})
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("MONGO_INITDB_ROOT_USERNAME", config["user"])
c.environment.add_env("MONGO_INITDB_ROOT_PASSWORD", config["password"])
c.environment.add_env("MONGO_INITDB_DATABASE", config["database"])
perms_instance.add_or_skip_action(
f"{self._name}_mongodb_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
self._get_repo(image, ("mongodb"))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for mongodb. Supported repos: {', '.join(supported_repos)}")
return repo
def get_port(self):
return self._config.get("port") or 27017
def get_url(self, variant: str):
user = urllib.parse.quote_plus(self._config["user"])
password = urllib.parse.quote_plus(self._config["password"])
creds = f"{user}:{password}"
addr = f"{self._name}:{self.get_port()}"
db = self._config["database"]
urls = {
"mongodb": f"mongodb://{creds}@{addr}/{db}",
"host_port": addr,
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]

View File

@@ -1,169 +0,0 @@
import urllib.parse
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise
class PostgresConfig(TypedDict):
user: str
password: str
database: str
port: NotRequired[int]
volume: "IxStorage"
additional_options: NotRequired[dict[str, str]]
MAX_POSTGRES_VERSION = 17
class PostgresContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: PostgresConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/var/lib/postgresql/data"
self._upgrade_name = f"{self._name}_upgrade"
self._upgrade_container = None
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for postgres")
port = valid_port_or_raise(self.get_port())
c = self._render_instance.add_container(name, image)
c.set_user(999, 999)
c.healthcheck.set_test("postgres", {"user": config["user"], "db": config["database"]})
c.set_shm_size_mb(256)
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
opts = []
for k, v in config.get("additional_options", {}).items():
opts.extend(["-c", f"{k}={v}"])
if opts:
c.set_command(opts)
common_variables = {
"POSTGRES_USER": config["user"],
"POSTGRES_PASSWORD": config["password"],
"POSTGRES_DB": config["database"],
"PGPORT": port,
}
for k, v in common_variables.items():
c.environment.add_env(k, v)
perms_instance.add_or_skip_action(
f"{self._name}_postgres_data", config["volume"], {"uid": 999, "gid": 999, "mode": "check"}
)
repo = self._get_repo(
image,
(
"postgres",
"postgis/postgis",
"pgvector/pgvector",
"tensorchord/pgvecto-rs",
"ghcr.io/immich-app/postgres",
),
)
# eg we don't want to handle upgrades of pg_vector at the moment
if repo == "postgres":
target_major_version = self._get_target_version(image)
upg = self._render_instance.add_container(self._upgrade_name, "postgres_upgrade_image")
upg.set_entrypoint(["/bin/bash", "-c", "/upgrade.sh"])
upg.restart.set_policy("on-failure", 1)
upg.set_user(999, 999)
upg.healthcheck.disable()
upg.remove_devices()
upg.add_storage(self._data_dir, config["volume"])
for k, v in common_variables.items():
upg.environment.add_env(k, v)
upg.environment.add_env("TARGET_VERSION", target_major_version)
upg.environment.add_env("DATA_DIR", self._data_dir)
self._upgrade_container = upg
c.depends.add_dependency(self._upgrade_name, "service_completed_successfully")
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def add_dependency(self, container_name: str, condition: str):
self._container.depends.add_dependency(container_name, condition)
if self._upgrade_container:
self._upgrade_container.depends.add_dependency(container_name, condition)
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for postgres. Supported repos: {', '.join(supported_repos)}")
return repo
def _get_target_version(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
tag = images[image].get("tag", "")
tag = str(tag) # Account for tags like 16.6
target_major_version = tag.split(".")[0]
try:
target_major_version = int(target_major_version)
except ValueError:
raise RenderError(f"Could not determine target major version from tag [{tag}]")
if target_major_version > MAX_POSTGRES_VERSION:
raise RenderError(f"Postgres version [{target_major_version}] is not supported")
return target_major_version
def get_port(self):
return self._config.get("port") or 5432
def get_url(self, variant: str):
user = urllib.parse.quote_plus(self._config["user"])
password = urllib.parse.quote_plus(self._config["password"])
creds = f"{user}:{password}"
addr = f"{self._name}:{self.get_port()}"
db = self._config["database"]
urls = {
"postgres": f"postgres://{creds}@{addr}/{db}?sslmode=disable",
"postgresql": f"postgresql://{creds}@{addr}/{db}?sslmode=disable",
"postgresql_no_creds": f"postgresql://{addr}/{db}?sslmode=disable",
"jdbc": f"jdbc:postgresql://{addr}/{db}",
"host_port": addr,
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]

View File

@@ -1,90 +0,0 @@
import urllib.parse
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise, valid_redis_password_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise, valid_redis_password_or_raise
class RedisConfig(TypedDict):
password: str
port: NotRequired[int]
volume: "IxStorage"
class RedisContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: RedisConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
for key in ("password", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for redis")
valid_redis_password_or_raise(config["password"])
port = valid_port_or_raise(self.get_port())
self._get_repo(image, ("redis", "valkey/valkey"))
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c = self._render_instance.add_container(name, image)
c.set_user(user, group)
c.remove_devices()
c.healthcheck.set_test("redis", {"password": config["password"]})
cmd = []
cmd.extend(["--port", str(port)])
cmd.extend(["--requirepass", config["password"]])
c.environment.add_env("REDIS_PASSWORD", config["password"])
c.set_command(cmd)
c.add_storage("/data", config["volume"])
perms_instance.add_or_skip_action(
f"{self._name}_redis_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for redis. Supported repos: {', '.join(supported_repos)}")
return repo
def get_port(self):
return self._config.get("port") or 6379
def get_url(self, variant: str):
addr = f"{self._name}:{self.get_port()}"
password = urllib.parse.quote_plus(self._config["password"])
match variant:
case "redis":
return f"redis://default:{password}@{addr}"
@property
def container(self):
return self._container

View File

@@ -1,85 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired, List
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class SolrConfig(TypedDict):
core: str
modules: NotRequired[List[str]]
port: NotRequired[int]
volume: "IxStorage"
class SolrContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: SolrConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/var/solr"
for key in ("core", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for solr")
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("curl", {"port": self.get_port(), "path": f"/solr/{config['core']}/admin/ping"})
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.set_command(["solr-precreate", config["core"]])
c.environment.add_env("SOLR_PORT", self.get_port())
if modules := config.get("modules"):
c.environment.add_env("SOLR_MODULES", ",".join(modules))
perms_instance.add_or_skip_action(
f"{self._name}_solr_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
self._get_repo(image, ("solr",))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for solr. Supported repos: {', '.join(supported_repos)}")
return repo
def get_port(self):
return self._config.get("port") or 8983
def get_url(self):
return f"http://{self._name}:{self.get_port()}/solr/{self._config['core']}"

View File

@@ -1,63 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
try:
from .error import RenderError
except ImportError:
from error import RenderError
class TikaConfig(TypedDict):
port: NotRequired[int]
class TikaContainer:
def __init__(self, render_instance: "Render", name: str, image: str, config: TikaConfig):
self._render_instance = render_instance
self._name = name
self._config = config
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("wget", {"port": self.get_port(), "path": "/tika", "spider": False})
c.remove_devices()
c.set_command(["--port", str(self.get_port())])
self._get_repo(image, ("apache/tika"))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for tika. Supported repos: {', '.join(supported_repos)}")
return repo
def get_port(self):
return self._config.get("port") or 9998
def get_url(self):
return f"http://{self._name}:{self.get_port()}"

View File

@@ -0,0 +1,98 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class ElasticConfig(TypedDict):
password: str
node_name: str
port: NotRequired[int]
volume: "IxStorage"
SUPPORTED_REPOS = ["elasticsearch"]
class ElasticSearchContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: ElasticConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/usr/share/elasticsearch/data"
for key in ("password", "node_name", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for ElasticSearch")
c = self._render_instance.add_container(name, image)
c.set_user(1000, 1000)
basic_auth_header = self._render_instance.funcs["basic_auth_header"]("elastic", config["password"])
c.healthcheck.set_test(
"curl",
{
"port": self.get_port(),
"path": "/_cluster/health?local=true",
"headers": [("Authorization", basic_auth_header)],
},
)
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("ELASTIC_PASSWORD", config["password"])
c.environment.add_env("http.port", self.get_port())
c.environment.add_env("path.data", self._data_dir)
c.environment.add_env("path.repo", self.get_snapshots_dir())
c.environment.add_env("node.name", config["node_name"])
c.environment.add_env("discovery.type", "single-node")
c.environment.add_env("xpack.security.enabled", True)
c.environment.add_env("xpack.security.transport.ssl.enabled", False)
perms_instance.add_or_skip_action(
f"{self._name}_elastic_data", config["volume"], {"uid": 1000, "gid": 1000, "mode": "check"}
)
self._get_repo(image)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(
f"Unsupported repo [{repo}] for elastic search. Supported repos: {', '.join(SUPPORTED_REPOS)}"
)
return repo
def get_port(self):
return self._config.get("port") or 9200
def get_url(self):
return f"http://{self._name}:{self.get_port()}"
def get_snapshots_dir(self):
return f"{self._data_dir}/snapshots"

View File

@@ -0,0 +1,94 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise
class MariadbConfig(TypedDict):
user: str
password: str
database: str
root_password: NotRequired[str]
port: NotRequired[int]
auto_upgrade: NotRequired[bool]
volume: "IxStorage"
SUPPORTED_REPOS = ["mariadb"]
class MariadbContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MariadbConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for mariadb")
port = valid_port_or_raise(self.get_port())
root_password = config.get("root_password") or config["password"]
auto_upgrade = config.get("auto_upgrade", True)
self._get_repo(image)
c = self._render_instance.add_container(name, image)
c.set_user(999, 999)
c.healthcheck.set_test("mariadb", {"password": root_password})
c.remove_devices()
c.add_storage("/var/lib/mysql", config["volume"])
perms_instance.add_or_skip_action(
f"{self._name}_mariadb_data", config["volume"], {"uid": 999, "gid": 999, "mode": "check"}
)
c.environment.add_env("MARIADB_USER", config["user"])
c.environment.add_env("MARIADB_PASSWORD", config["password"])
c.environment.add_env("MARIADB_ROOT_PASSWORD", root_password)
c.environment.add_env("MARIADB_DATABASE", config["database"])
c.environment.add_env("MARIADB_AUTO_UPGRADE", str(auto_upgrade).lower())
c.set_command(["--port", str(port)])
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(f"Unsupported repo [{repo}] for mariadb. Supported repos: {', '.join(SUPPORTED_REPOS)}")
return repo
def get_url(self, variant: str):
addr = f"{self._name}:{self.get_port()}"
urls = {
"jdbc": f"jdbc:mariadb://{addr}/{self._config['database']}",
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]
def get_port(self):
return self._config.get("port") or 3306
@property
def container(self):
return self._container

View File

@@ -0,0 +1,88 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class MeiliConfig(TypedDict):
master_key: str
port: NotRequired[int]
volume: "IxStorage"
SUPPORTED_REPOS = ["getmeili/meilisearch"]
class MeilisearchContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MeiliConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/meili_data"
for key in ("master_key", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for meilisearch")
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("curl", {"port": self.get_port(), "path": "/health"})
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("MEILI_HTTP_ADDR", f"0.0.0.0:{self.get_port()}")
c.environment.add_env("MEILI_NO_ANALYTICS", True)
c.environment.add_env("MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE", True)
c.environment.add_env("MEILI_MASTER_KEY", config["master_key"])
perms_instance.add_or_skip_action(
f"{self._name}_meili_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
self._get_repo(image)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(
f"Unsupported repo [{repo}] for meilisearch. Supported repos: {', '.join(SUPPORTED_REPOS)}"
)
return repo
def get_port(self):
return self._config.get("port") or 7700
def get_url(self):
return f"http://{self._name}:{self.get_port()}"

View File

@@ -0,0 +1,100 @@
import urllib.parse
from typing import TYPE_CHECKING, TypedDict
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class MongoDBConfig(TypedDict):
user: str
password: str
database: str
volume: "IxStorage"
SUPPORTED_REPOS = ["mongo"]
class MongoDBContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MongoDBConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/data/db"
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for mongodb")
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("mongodb", {"db": config["database"]})
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("MONGO_INITDB_ROOT_USERNAME", config["user"])
c.environment.add_env("MONGO_INITDB_ROOT_PASSWORD", config["password"])
c.environment.add_env("MONGO_INITDB_DATABASE", config["database"])
perms_instance.add_or_skip_action(
f"{self._name}_mongodb_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
self._get_repo(image)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(f"Unsupported repo [{repo}] for mongodb. Supported repos: {', '.join(SUPPORTED_REPOS)}")
return repo
def get_port(self):
return self._config.get("port") or 27017
def get_url(self, variant: str):
user = urllib.parse.quote_plus(self._config["user"])
password = urllib.parse.quote_plus(self._config["password"])
creds = f"{user}:{password}"
addr = f"{self._name}:{self.get_port()}"
db = self._config["database"]
urls = {
"mongodb": f"mongodb://{creds}@{addr}/{db}",
"host_port": addr,
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]

View File

@@ -0,0 +1,214 @@
import re
import urllib.parse
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise
class PostgresConfig(TypedDict):
user: str
password: str
database: str
port: NotRequired[int]
volume: "IxStorage"
additional_options: NotRequired[dict[str, str]]
MAX_POSTGRES_VERSION = 18
SUPPORTED_REPOS = [
"postgres",
"postgis/postgis",
"pgvector/pgvector",
"ghcr.io/immich-app/postgres",
]
def get_major_version(variant: str, tag: str):
if variant == "postgres":
# 17.7-bookworm
regex = re.compile(r"^\d+\.\d+-\w+")
def oper(x):
return x.split(".")[0]
elif variant == "postgis/postgis":
# 17-3.5
regex = re.compile(r"^\d+\-\d+\.\d+")
def oper(x):
return x.split("-")[0]
elif variant == "pgvector/pgvector":
# 0.8.1-pg17
regex = re.compile(r"^\d+\.\d+\.\d+\-pg\d+")
def oper(x):
return x.split("-")[1].lstrip("pg")
elif variant == "ghcr.io/immich-app/postgres":
# 15-vectorchord0.4.3-pgvectors0.2.0
regex = re.compile(r"^\d+\-vectorchord\d+\.\d+\.\d+(\-pgvectors?\d+\.\d+\.\d+)?")
def oper(x):
return x.split("-")[0]
if not regex.match(tag):
raise RenderError(f"Could not determine major version from tag [{tag}] for variant [{variant}]")
return oper(tag)
class PostgresContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: PostgresConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = None
self._upgrade_name = f"{self._name}_upgrade"
self._upgrade_container = None
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for postgres")
port = valid_port_or_raise(self.get_port())
# TODO: Set some defaults for ZFS Optimizations (Need to check if applies on updates)
# https://vadosware.io/post/everything-ive-seen-on-optimizing-postgres-on-zfs-on-linux/
opts = []
for k, v in config.get("additional_options", {}).items():
opts.extend(["-c", f"{k}={v}"])
common_variables = {
"POSTGRES_USER": config["user"],
"POSTGRES_PASSWORD": config["password"],
"POSTGRES_DB": config["database"],
"PGPORT": port,
}
c = self._render_instance.add_container(name, image)
c.healthcheck.set_test("postgres", {"user": config["user"], "db": config["database"]})
c.set_shm_size_mb(256)
if opts:
c.set_command(opts)
containers = [c]
# eg we don't want to handle upgrades of pg_vector or immich at the moment
repo = self._get_repo(image)
if repo == "postgres":
self._data_dir = "/var/lib/postgresql"
target_major_version = self._get_target_version(image)
# This is the new format upstream Postgres uses/suggests.
# E.g., for Postgres 17, the data dir is /var/lib/postgresql/17/docker
common_variables.update({"PGDATA": f"{self._data_dir}/{target_major_version}/docker"})
upg = self._render_instance.add_container(self._upgrade_name, "postgres_upgrade_image")
self._upgrade_container = upg
containers.append(upg)
upg.set_entrypoint(["/bin/bash", "-c", "/upgrade.sh"])
upg.restart.set_policy("on-failure", 1)
upg.healthcheck.disable()
upg.setup_as_helper(profile="medium")
upg.environment.add_env("TARGET_VERSION", target_major_version)
c.depends.add_dependency(self._upgrade_name, "service_completed_successfully")
else:
self._data_dir = "/var/lib/postgresql/data"
for container in containers:
# TODO: We can now use 568:568 (or any user/group).
# Need to first plan a migration path for the existing users.
container.set_user(999, 999)
container.remove_devices()
container.add_storage(self._data_dir, config["volume"])
for k, v in common_variables.items():
container.environment.add_env(k, v)
perms_instance.add_or_skip_action(
f"{self._name}_postgres_data", config["volume"], {"uid": 999, "gid": 999, "mode": "check"}
)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def add_dependency(self, container_name: str, condition: str):
self._container.depends.add_dependency(container_name, condition)
if self._upgrade_container:
self._upgrade_container.depends.add_dependency(container_name, condition)
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(f"Unsupported repo [{repo}] for postgres. Supported repos: {', '.join(SUPPORTED_REPOS)}")
return repo
def _get_target_version(self, image):
repo = self._get_repo(image)
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
tag = str(images[image].get("tag", ""))
target_major_version = get_major_version(repo, tag)
try:
# Make sure we end up with an integer
target_major_version = int(target_major_version)
except ValueError:
raise RenderError(f"Could not determine target major version from tag [{tag}]")
if target_major_version > MAX_POSTGRES_VERSION:
raise RenderError(f"Postgres version [{target_major_version}] is not supported")
return target_major_version
def get_port(self):
return self._config.get("port") or 5432
def get_url(self, variant: str):
user = urllib.parse.quote_plus(self._config["user"])
password = urllib.parse.quote_plus(self._config["password"])
creds = f"{user}:{password}"
addr = f"{self._name}:{self.get_port()}"
db = self._config["database"]
urls = {
"postgres": f"postgres://{creds}@{addr}/{db}?sslmode=disable",
"postgresql": f"postgresql://{creds}@{addr}/{db}?sslmode=disable",
"postgresql_no_creds": f"postgresql://{addr}/{db}?sslmode=disable",
"jdbc": f"jdbc:postgresql://{addr}/{db}",
"host_port": addr,
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]

View File

@@ -0,0 +1,93 @@
import urllib.parse
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise, valid_redis_password_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise, valid_redis_password_or_raise
class RedisConfig(TypedDict):
password: str
port: NotRequired[int]
volume: "IxStorage"
SUPPORTED_REPOS = ["redis", "valkey/valkey"]
class RedisContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: RedisConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
for key in ("password", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for redis")
valid_redis_password_or_raise(config["password"])
port = valid_port_or_raise(self.get_port())
self._get_repo(image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c = self._render_instance.add_container(name, image)
c.set_user(user, group)
c.remove_devices()
c.healthcheck.set_test("redis", {"password": config["password"]})
cmd = []
cmd.extend(["--port", str(port)])
cmd.extend(["--requirepass", config["password"]])
c.environment.add_env("REDIS_PASSWORD", config["password"])
c.set_command(cmd)
c.add_storage("/data", config["volume"])
perms_instance.add_or_skip_action(
f"{self._name}_redis_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(f"Unsupported repo [{repo}] for redis. Supported repos: {', '.join(SUPPORTED_REPOS)}")
return repo
def get_port(self):
return self._config.get("port") or 6379
def get_url(self, variant: str):
addr = f"{self._name}:{self.get_port()}"
password = urllib.parse.quote_plus(self._config["password"])
match variant:
case "redis":
return f"redis://default:{password}@{addr}"
@property
def container(self):
return self._container

View File

@@ -0,0 +1,88 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired, List
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class SolrConfig(TypedDict):
core: str
modules: NotRequired[List[str]]
port: NotRequired[int]
volume: "IxStorage"
SUPPORTED_REPOS = ["solr"]
class SolrContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: SolrConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/var/solr"
for key in ("core", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for solr")
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("curl", {"port": self.get_port(), "path": f"/solr/{config['core']}/admin/ping"})
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.set_command(["solr-precreate", config["core"]])
c.environment.add_env("SOLR_PORT", self.get_port())
if modules := config.get("modules"):
c.environment.add_env("SOLR_MODULES", ",".join(modules))
perms_instance.add_or_skip_action(
f"{self._name}_solr_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
self._get_repo(image)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(f"Unsupported repo [{repo}] for solr. Supported repos: {', '.join(SUPPORTED_REPOS)}")
return repo
def get_port(self):
return self._config.get("port") or 8983
def get_url(self):
return f"http://{self._name}:{self.get_port()}/solr/{self._config['core']}"

View File

@@ -0,0 +1,66 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
try:
from .error import RenderError
except ImportError:
from error import RenderError
class TikaConfig(TypedDict):
port: NotRequired[int]
SUPPORTED_REPOS = ["apache/tika"]
class TikaContainer:
def __init__(self, render_instance: "Render", name: str, image: str, config: TikaConfig):
self._render_instance = render_instance
self._name = name
self._config = config
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("wget", {"port": self.get_port(), "path": "/tika", "spider": False})
c.remove_devices()
c.set_command(["--port", str(self.get_port())])
self._get_repo(image)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(f"Unsupported repo [{repo}] for tika. Supported repos: {', '.join(SUPPORTED_REPOS)}")
return repo
def get_port(self):
return self._config.get("port") or 9998
def get_url(self):
return f"http://{self._name}:{self.get_port()}"

File diff suppressed because it is too large Load Diff

View File

@@ -24,8 +24,8 @@ icon: https://media.sys.truenas.net/apps/adguard-home/icons/icon.svg
keywords:
- dns
- adblock
lib_version: 2.1.64
lib_version_hash: 920ca771628a09b7f9a11cd950795d5fcfc7a4e3a3f90e3da49c6ffe3f6c6e1e
lib_version: 2.1.65
lib_version_hash: f92a9ee78c78fc77f86e7d8b545bd4c605c31c599e2c5da59f1615aa516cb8b5
maintainers:
- email: dev@truenas.com
name: truenas
@@ -45,4 +45,4 @@ sources:
- https://hub.docker.com/r/adguard/adguardhome
title: AdGuard Home
train: community
version: 1.2.19
version: 1.2.20

View File

@@ -1,95 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class ElasticConfig(TypedDict):
password: str
node_name: str
port: NotRequired[int]
volume: "IxStorage"
class ElasticSearchContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: ElasticConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/usr/share/elasticsearch/data"
for key in ("password", "node_name", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for ElasticSearch")
c = self._render_instance.add_container(name, image)
c.set_user(1000, 1000)
basic_auth_header = self._render_instance.funcs["basic_auth_header"]("elastic", config["password"])
c.healthcheck.set_test(
"curl",
{
"port": self.get_port(),
"path": "/_cluster/health?local=true",
"headers": [("Authorization", basic_auth_header)],
},
)
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("ELASTIC_PASSWORD", config["password"])
c.environment.add_env("http.port", self.get_port())
c.environment.add_env("path.data", self._data_dir)
c.environment.add_env("path.repo", self.get_snapshots_dir())
c.environment.add_env("node.name", config["node_name"])
c.environment.add_env("discovery.type", "single-node")
c.environment.add_env("xpack.security.enabled", True)
c.environment.add_env("xpack.security.transport.ssl.enabled", False)
perms_instance.add_or_skip_action(
f"{self._name}_elastic_data", config["volume"], {"uid": 1000, "gid": 1000, "mode": "check"}
)
self._get_repo(image, ("docker.elastic.co/elasticsearch/elasticsearch"))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(
f"Unsupported repo [{repo}] for elastic search. Supported repos: {', '.join(supported_repos)}"
)
return repo
def get_port(self):
return self._config.get("port") or 9200
def get_url(self):
return f"http://{self._name}:{self.get_port()}"
def get_snapshots_dir(self):
return f"{self._data_dir}/snapshots"

View File

@@ -1,91 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise
class MariadbConfig(TypedDict):
user: str
password: str
database: str
root_password: NotRequired[str]
port: NotRequired[int]
auto_upgrade: NotRequired[bool]
volume: "IxStorage"
class MariadbContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MariadbConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for mariadb")
port = valid_port_or_raise(self.get_port())
root_password = config.get("root_password") or config["password"]
auto_upgrade = config.get("auto_upgrade", True)
self._get_repo(image, ("mariadb"))
c = self._render_instance.add_container(name, image)
c.set_user(999, 999)
c.healthcheck.set_test("mariadb", {"password": root_password})
c.remove_devices()
c.add_storage("/var/lib/mysql", config["volume"])
perms_instance.add_or_skip_action(
f"{self._name}_mariadb_data", config["volume"], {"uid": 999, "gid": 999, "mode": "check"}
)
c.environment.add_env("MARIADB_USER", config["user"])
c.environment.add_env("MARIADB_PASSWORD", config["password"])
c.environment.add_env("MARIADB_ROOT_PASSWORD", root_password)
c.environment.add_env("MARIADB_DATABASE", config["database"])
c.environment.add_env("MARIADB_AUTO_UPGRADE", str(auto_upgrade).lower())
c.set_command(["--port", str(port)])
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for mariadb. Supported repos: {', '.join(supported_repos)}")
return repo
def get_url(self, variant: str):
addr = f"{self._name}:{self.get_port()}"
urls = {
"jdbc": f"jdbc:mariadb://{addr}/{self._config['database']}",
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]
def get_port(self):
return self._config.get("port") or 3306
@property
def container(self):
return self._container

View File

@@ -1,85 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class MeiliConfig(TypedDict):
master_key: str
port: NotRequired[int]
volume: "IxStorage"
class MeilisearchContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MeiliConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/meili_data"
for key in ("master_key", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for meilisearch")
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("curl", {"port": self.get_port(), "path": "/health"})
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("MEILI_HTTP_ADDR", f"0.0.0.0:{self.get_port()}")
c.environment.add_env("MEILI_NO_ANALYTICS", True)
c.environment.add_env("MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE", True)
c.environment.add_env("MEILI_MASTER_KEY", config["master_key"])
perms_instance.add_or_skip_action(
f"{self._name}_meili_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
self._get_repo(image, ("getmeili/meilisearch",))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(
f"Unsupported repo [{repo}] for meilisearch. Supported repos: {', '.join(supported_repos)}"
)
return repo
def get_port(self):
return self._config.get("port") or 7700
def get_url(self):
return f"http://{self._name}:{self.get_port()}"

View File

@@ -1,97 +0,0 @@
import urllib.parse
from typing import TYPE_CHECKING, TypedDict
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class MongoDBConfig(TypedDict):
user: str
password: str
database: str
volume: "IxStorage"
class MongoDBContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MongoDBConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/data/db"
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for mongodb")
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("mongodb", {"db": config["database"]})
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("MONGO_INITDB_ROOT_USERNAME", config["user"])
c.environment.add_env("MONGO_INITDB_ROOT_PASSWORD", config["password"])
c.environment.add_env("MONGO_INITDB_DATABASE", config["database"])
perms_instance.add_or_skip_action(
f"{self._name}_mongodb_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
self._get_repo(image, ("mongodb"))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for mongodb. Supported repos: {', '.join(supported_repos)}")
return repo
def get_port(self):
return self._config.get("port") or 27017
def get_url(self, variant: str):
user = urllib.parse.quote_plus(self._config["user"])
password = urllib.parse.quote_plus(self._config["password"])
creds = f"{user}:{password}"
addr = f"{self._name}:{self.get_port()}"
db = self._config["database"]
urls = {
"mongodb": f"mongodb://{creds}@{addr}/{db}",
"host_port": addr,
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]

View File

@@ -1,169 +0,0 @@
import urllib.parse
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise
class PostgresConfig(TypedDict):
user: str
password: str
database: str
port: NotRequired[int]
volume: "IxStorage"
additional_options: NotRequired[dict[str, str]]
MAX_POSTGRES_VERSION = 17
class PostgresContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: PostgresConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/var/lib/postgresql/data"
self._upgrade_name = f"{self._name}_upgrade"
self._upgrade_container = None
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for postgres")
port = valid_port_or_raise(self.get_port())
c = self._render_instance.add_container(name, image)
c.set_user(999, 999)
c.healthcheck.set_test("postgres", {"user": config["user"], "db": config["database"]})
c.set_shm_size_mb(256)
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
opts = []
for k, v in config.get("additional_options", {}).items():
opts.extend(["-c", f"{k}={v}"])
if opts:
c.set_command(opts)
common_variables = {
"POSTGRES_USER": config["user"],
"POSTGRES_PASSWORD": config["password"],
"POSTGRES_DB": config["database"],
"PGPORT": port,
}
for k, v in common_variables.items():
c.environment.add_env(k, v)
perms_instance.add_or_skip_action(
f"{self._name}_postgres_data", config["volume"], {"uid": 999, "gid": 999, "mode": "check"}
)
repo = self._get_repo(
image,
(
"postgres",
"postgis/postgis",
"pgvector/pgvector",
"tensorchord/pgvecto-rs",
"ghcr.io/immich-app/postgres",
),
)
# eg we don't want to handle upgrades of pg_vector at the moment
if repo == "postgres":
target_major_version = self._get_target_version(image)
upg = self._render_instance.add_container(self._upgrade_name, "postgres_upgrade_image")
upg.set_entrypoint(["/bin/bash", "-c", "/upgrade.sh"])
upg.restart.set_policy("on-failure", 1)
upg.set_user(999, 999)
upg.healthcheck.disable()
upg.remove_devices()
upg.add_storage(self._data_dir, config["volume"])
for k, v in common_variables.items():
upg.environment.add_env(k, v)
upg.environment.add_env("TARGET_VERSION", target_major_version)
upg.environment.add_env("DATA_DIR", self._data_dir)
self._upgrade_container = upg
c.depends.add_dependency(self._upgrade_name, "service_completed_successfully")
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def add_dependency(self, container_name: str, condition: str):
self._container.depends.add_dependency(container_name, condition)
if self._upgrade_container:
self._upgrade_container.depends.add_dependency(container_name, condition)
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for postgres. Supported repos: {', '.join(supported_repos)}")
return repo
def _get_target_version(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
tag = images[image].get("tag", "")
tag = str(tag) # Account for tags like 16.6
target_major_version = tag.split(".")[0]
try:
target_major_version = int(target_major_version)
except ValueError:
raise RenderError(f"Could not determine target major version from tag [{tag}]")
if target_major_version > MAX_POSTGRES_VERSION:
raise RenderError(f"Postgres version [{target_major_version}] is not supported")
return target_major_version
def get_port(self):
return self._config.get("port") or 5432
def get_url(self, variant: str):
user = urllib.parse.quote_plus(self._config["user"])
password = urllib.parse.quote_plus(self._config["password"])
creds = f"{user}:{password}"
addr = f"{self._name}:{self.get_port()}"
db = self._config["database"]
urls = {
"postgres": f"postgres://{creds}@{addr}/{db}?sslmode=disable",
"postgresql": f"postgresql://{creds}@{addr}/{db}?sslmode=disable",
"postgresql_no_creds": f"postgresql://{addr}/{db}?sslmode=disable",
"jdbc": f"jdbc:postgresql://{addr}/{db}",
"host_port": addr,
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]

View File

@@ -1,90 +0,0 @@
import urllib.parse
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise, valid_redis_password_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise, valid_redis_password_or_raise
class RedisConfig(TypedDict):
password: str
port: NotRequired[int]
volume: "IxStorage"
class RedisContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: RedisConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
for key in ("password", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for redis")
valid_redis_password_or_raise(config["password"])
port = valid_port_or_raise(self.get_port())
self._get_repo(image, ("redis", "valkey/valkey"))
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c = self._render_instance.add_container(name, image)
c.set_user(user, group)
c.remove_devices()
c.healthcheck.set_test("redis", {"password": config["password"]})
cmd = []
cmd.extend(["--port", str(port)])
cmd.extend(["--requirepass", config["password"]])
c.environment.add_env("REDIS_PASSWORD", config["password"])
c.set_command(cmd)
c.add_storage("/data", config["volume"])
perms_instance.add_or_skip_action(
f"{self._name}_redis_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for redis. Supported repos: {', '.join(supported_repos)}")
return repo
def get_port(self):
return self._config.get("port") or 6379
def get_url(self, variant: str):
addr = f"{self._name}:{self.get_port()}"
password = urllib.parse.quote_plus(self._config["password"])
match variant:
case "redis":
return f"redis://default:{password}@{addr}"
@property
def container(self):
return self._container

View File

@@ -1,85 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired, List
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class SolrConfig(TypedDict):
core: str
modules: NotRequired[List[str]]
port: NotRequired[int]
volume: "IxStorage"
class SolrContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: SolrConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/var/solr"
for key in ("core", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for solr")
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("curl", {"port": self.get_port(), "path": f"/solr/{config['core']}/admin/ping"})
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.set_command(["solr-precreate", config["core"]])
c.environment.add_env("SOLR_PORT", self.get_port())
if modules := config.get("modules"):
c.environment.add_env("SOLR_MODULES", ",".join(modules))
perms_instance.add_or_skip_action(
f"{self._name}_solr_data", config["volume"], {"uid": user, "gid": group, "mode": "check"}
)
self._get_repo(image, ("solr",))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for solr. Supported repos: {', '.join(supported_repos)}")
return repo
def get_port(self):
return self._config.get("port") or 8983
def get_url(self):
return f"http://{self._name}:{self.get_port()}/solr/{self._config['core']}"

View File

@@ -1,63 +0,0 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
try:
from .error import RenderError
except ImportError:
from error import RenderError
class TikaConfig(TypedDict):
port: NotRequired[int]
class TikaContainer:
def __init__(self, render_instance: "Render", name: str, image: str, config: TikaConfig):
self._render_instance = render_instance
self._name = name
self._config = config
c = self._render_instance.add_container(name, image)
user, group = 568, 568
run_as = self._render_instance.values.get("run_as")
if run_as:
user = run_as["user"] or user # Avoids running as root
group = run_as["group"] or group # Avoids running as root
c.set_user(user, group)
c.healthcheck.set_test("wget", {"port": self.get_port(), "path": "/tika", "spider": False})
c.remove_devices()
c.set_command(["--port", str(self.get_port())])
self._get_repo(image, ("apache/tika"))
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image, supported_repos):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in supported_repos:
raise RenderError(f"Unsupported repo [{repo}] for tika. Supported repos: {', '.join(supported_repos)}")
return repo
def get_port(self):
return self._config.get("port") or 9998
def get_url(self):
return f"http://{self._name}:{self.get_port()}"

View File

@@ -0,0 +1,98 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
class ElasticConfig(TypedDict):
password: str
node_name: str
port: NotRequired[int]
volume: "IxStorage"
SUPPORTED_REPOS = ["elasticsearch"]
class ElasticSearchContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: ElasticConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
self._data_dir = "/usr/share/elasticsearch/data"
for key in ("password", "node_name", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for ElasticSearch")
c = self._render_instance.add_container(name, image)
c.set_user(1000, 1000)
basic_auth_header = self._render_instance.funcs["basic_auth_header"]("elastic", config["password"])
c.healthcheck.set_test(
"curl",
{
"port": self.get_port(),
"path": "/_cluster/health?local=true",
"headers": [("Authorization", basic_auth_header)],
},
)
c.remove_devices()
c.add_storage(self._data_dir, config["volume"])
c.environment.add_env("ELASTIC_PASSWORD", config["password"])
c.environment.add_env("http.port", self.get_port())
c.environment.add_env("path.data", self._data_dir)
c.environment.add_env("path.repo", self.get_snapshots_dir())
c.environment.add_env("node.name", config["node_name"])
c.environment.add_env("discovery.type", "single-node")
c.environment.add_env("xpack.security.enabled", True)
c.environment.add_env("xpack.security.transport.ssl.enabled", False)
perms_instance.add_or_skip_action(
f"{self._name}_elastic_data", config["volume"], {"uid": 1000, "gid": 1000, "mode": "check"}
)
self._get_repo(image)
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
@property
def container(self):
return self._container
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(
f"Unsupported repo [{repo}] for elastic search. Supported repos: {', '.join(SUPPORTED_REPOS)}"
)
return repo
def get_port(self):
return self._config.get("port") or 9200
def get_url(self):
return f"http://{self._name}:{self.get_port()}"
def get_snapshots_dir(self):
return f"{self._data_dir}/snapshots"

View File

@@ -0,0 +1,94 @@
from typing import TYPE_CHECKING, TypedDict, NotRequired
if TYPE_CHECKING:
from render import Render
from storage import IxStorage
try:
from .error import RenderError
from .deps_perms import PermsContainer
from .validations import valid_port_or_raise
except ImportError:
from error import RenderError
from deps_perms import PermsContainer
from validations import valid_port_or_raise
class MariadbConfig(TypedDict):
user: str
password: str
database: str
root_password: NotRequired[str]
port: NotRequired[int]
auto_upgrade: NotRequired[bool]
volume: "IxStorage"
SUPPORTED_REPOS = ["mariadb"]
class MariadbContainer:
def __init__(
self, render_instance: "Render", name: str, image: str, config: MariadbConfig, perms_instance: PermsContainer
):
self._render_instance = render_instance
self._name = name
self._config = config
for key in ("user", "password", "database", "volume"):
if key not in config:
raise RenderError(f"Expected [{key}] to be set for mariadb")
port = valid_port_or_raise(self.get_port())
root_password = config.get("root_password") or config["password"]
auto_upgrade = config.get("auto_upgrade", True)
self._get_repo(image)
c = self._render_instance.add_container(name, image)
c.set_user(999, 999)
c.healthcheck.set_test("mariadb", {"password": root_password})
c.remove_devices()
c.add_storage("/var/lib/mysql", config["volume"])
perms_instance.add_or_skip_action(
f"{self._name}_mariadb_data", config["volume"], {"uid": 999, "gid": 999, "mode": "check"}
)
c.environment.add_env("MARIADB_USER", config["user"])
c.environment.add_env("MARIADB_PASSWORD", config["password"])
c.environment.add_env("MARIADB_ROOT_PASSWORD", root_password)
c.environment.add_env("MARIADB_DATABASE", config["database"])
c.environment.add_env("MARIADB_AUTO_UPGRADE", str(auto_upgrade).lower())
c.set_command(["--port", str(port)])
# Store container for further configuration
# For example: c.depends.add_dependency("other_container", "service_started")
self._container = c
def _get_repo(self, image):
images = self._render_instance.values["images"]
if image not in images:
raise RenderError(f"Image [{image}] not found in values. Available images: [{', '.join(images.keys())}]")
repo = images[image].get("repository")
if not repo:
raise RenderError("Could not determine repo")
if repo not in SUPPORTED_REPOS:
raise RenderError(f"Unsupported repo [{repo}] for mariadb. Supported repos: {', '.join(SUPPORTED_REPOS)}")
return repo
def get_url(self, variant: str):
addr = f"{self._name}:{self.get_port()}"
urls = {
"jdbc": f"jdbc:mariadb://{addr}/{self._config['database']}",
}
if variant not in urls:
raise RenderError(f"Expected [variant] to be one of [{', '.join(urls.keys())}], got [{variant}]")
return urls[variant]
def get_port(self):
return self._config.get("port") or 3306
@property
def container(self):
return self._container

Some files were not shown because too many files have changed in this diff Show More