mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 10:52:22 +00:00
fix(oci): services show their address instead of a web link, and applications are not asked for their own
This commit is contained in:
@@ -117,7 +117,8 @@
|
||||
},
|
||||
"environment_overrides": {
|
||||
"APP_URL": {
|
||||
"prompt": "Address BookStack will be opened at (for example http://192.168.1.50:6875)"
|
||||
"prompt": "Address BookStack will be opened at (for example http://192.168.1.50)",
|
||||
"container_address": "http://@CONTAINER_ADDRESS@"
|
||||
},
|
||||
"DB_HOST": {
|
||||
"prompt": "Address of the MariaDB server"
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
{
|
||||
"catalog_ui": {
|
||||
"launch": {
|
||||
"port": null
|
||||
}
|
||||
},
|
||||
"first_run": {
|
||||
"endpoints": []
|
||||
},
|
||||
"proxmox": {
|
||||
"installer_profile": {
|
||||
"volume_preparations": [
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"required": false
|
||||
},
|
||||
"SITE_ROOT": {
|
||||
"prompt": "Address Healthchecks will be opened at (for example http://192.168.1.50:8000)"
|
||||
"prompt": "Address Healthchecks will be opened at (for example http://192.168.1.50:8000)",
|
||||
"container_address": "http://@CONTAINER_ADDRESS@:8000"
|
||||
},
|
||||
"SITE_NAME": {
|
||||
"example": "Healthchecks"
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
{
|
||||
"catalog_ui": {
|
||||
"launch": {
|
||||
"port": null
|
||||
}
|
||||
},
|
||||
"first_run": {
|
||||
"endpoints": []
|
||||
},
|
||||
"proxmox": {
|
||||
"installer_profile": {
|
||||
"completion_notes": [
|
||||
"MariaDB has no web interface: applications and clients connect to port 3306 of this container.",
|
||||
"The root password is asked for when connecting from another machine. Inside the container, the mariadb command opens as root without a password."
|
||||
],
|
||||
"volume_preparations": [
|
||||
{
|
||||
"container_path": "/config",
|
||||
|
||||
@@ -215,7 +215,8 @@
|
||||
"prompt": "Email address of the admin account"
|
||||
},
|
||||
"ALLOWED_HOST": {
|
||||
"prompt": "Name or address NetBox will be opened at (for example netbox.example.com)"
|
||||
"prompt": "Name or address NetBox will be opened at (for example netbox.example.com)",
|
||||
"container_address": "@CONTAINER_ADDRESS@"
|
||||
},
|
||||
"DB_HOST": {
|
||||
"prompt": "Address of the PostgreSQL server"
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
{
|
||||
"catalog_ui": {
|
||||
"launch": {
|
||||
"port": null
|
||||
}
|
||||
},
|
||||
"first_run": {
|
||||
"endpoints": []
|
||||
},
|
||||
"proxmox": {
|
||||
"installer_profile": {
|
||||
"volume_preparations": [
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
{
|
||||
"catalog_ui": {
|
||||
"launch": {
|
||||
"port": null
|
||||
}
|
||||
},
|
||||
"first_run": {
|
||||
"endpoints": []
|
||||
},
|
||||
"proxmox": {
|
||||
"installer_profile": {
|
||||
"volume_preparations": [
|
||||
|
||||
@@ -142,7 +142,8 @@
|
||||
},
|
||||
"environment_overrides": {
|
||||
"APP_URL": {
|
||||
"prompt": "Address Speedtest Tracker will be opened at (for example http://192.168.1.50)"
|
||||
"prompt": "Address Speedtest Tracker will be opened at (for example http://192.168.1.50)",
|
||||
"container_address": "http://@CONTAINER_ADDRESS@"
|
||||
},
|
||||
"SPEEDTEST_SCHEDULE": {
|
||||
"example": "0 */6 * * *",
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
{
|
||||
"catalog_ui": {
|
||||
"launch": {
|
||||
"port": null
|
||||
}
|
||||
},
|
||||
"first_run": {
|
||||
"endpoints": []
|
||||
},
|
||||
"proxmox": {
|
||||
"installer_profile": {
|
||||
"volume_preparations": [
|
||||
|
||||
@@ -2060,6 +2060,60 @@ CONSOLE_STATE=$(python3 "${SCRIPT_DIR}/oci_console.py" configure "$VMID") \
|
||||
|| die "$(translate "The console of the container could not be configured")"
|
||||
RUNTIME_CONSOLE_LOG=$(jq -r '.log' <<<"$CONSOLE_STATE")
|
||||
|
||||
# A variable that holds the application's own address takes the one of the
|
||||
# container: the fixed one, or the one DHCP gives it. That one is asked for
|
||||
# with the application not started yet, so that its first start already has it.
|
||||
OWN_ADDRESS_MARK='@CONTAINER_ADDRESS@'
|
||||
OWN_ADDRESS=""
|
||||
apply_own_address() {
|
||||
local names name value original inert="" candidate attempt
|
||||
names=$(jq -r --arg mark "$OWN_ADDRESS_MARK" \
|
||||
'.environment[]? | select(.value | contains($mark)) | .name' "$DEPLOYMENT_FILE")
|
||||
[[ -n $names ]] || return 0
|
||||
msg_info "$(translate "Waiting for the network address...")"
|
||||
if [[ -n ${HOST_MONITOR:-} ]]; then
|
||||
OWN_ADDRESS=$HOST_MONITOR_IP
|
||||
elif [[ $IPV4 != dhcp ]]; then
|
||||
OWN_ADDRESS=${IPV4%%/*}
|
||||
else
|
||||
mount_ct_rootfs
|
||||
for candidate in /bin/sleep /usr/bin/sleep; do
|
||||
if [[ -e /var/lib/lxc/${VMID}/rootfs${candidate} || -L /var/lib/lxc/${VMID}/rootfs${candidate} ]]; then
|
||||
inert=$candidate
|
||||
break
|
||||
fi
|
||||
done
|
||||
oci_quiet pct unmount "$VMID" || true
|
||||
if [[ -z $inert ]]; then
|
||||
oci_log "The image has no sleep command to wait for the DHCP address with"
|
||||
die "$(translate "No LAN address was obtained")"
|
||||
fi
|
||||
original=$(pct config "$VMID" | sed -n 's/^entrypoint: //p')
|
||||
oci_quiet pct set "$VMID" --entrypoint "$inert 120" \
|
||||
|| die "$(translate "Could not set the container entrypoint")"
|
||||
oci_quiet pct start "$VMID" || die "$(translate "The container could not be started:") CT $VMID"
|
||||
for attempt in $(seq 1 60); do
|
||||
OWN_ADDRESS=$(own_ipv4 2>/dev/null) \
|
||||
|| OWN_ADDRESS=$(lxc-info -n "$VMID" -iH 2>/dev/null | grep -m1 -E '^[0-9]+\.' || true)
|
||||
[[ -n $OWN_ADDRESS ]] && break
|
||||
sleep 0.5
|
||||
done
|
||||
oci_quiet pct stop "$VMID" || die "$(translate "The container could not be started:") CT $VMID"
|
||||
if [[ -n $original ]]; then
|
||||
oci_quiet pct set "$VMID" --entrypoint "$original" || die "$(translate "Could not set the container entrypoint")"
|
||||
else
|
||||
oci_quiet pct set "$VMID" --delete entrypoint || die "$(translate "Could not set the container entrypoint")"
|
||||
fi
|
||||
fi
|
||||
[[ $OWN_ADDRESS =~ ^[0-9]+(\.[0-9]+){3}$ ]] || die "$(translate "No LAN address was obtained")"
|
||||
while IFS= read -r name; do
|
||||
value=$(jq -r --arg name "$name" '[.environment[] | select(.name == $name) | .value] | last' "$DEPLOYMENT_FILE")
|
||||
set_runtime_environment "$name" "${value//$OWN_ADDRESS_MARK/$OWN_ADDRESS}"
|
||||
done <<<"$names"
|
||||
msg_ok "$(translate "IP address:") $OWN_ADDRESS"
|
||||
}
|
||||
apply_own_address
|
||||
|
||||
oci_log "Configuration created for CT $VMID"
|
||||
PASSWORD_STATE=""
|
||||
if [[ $START_AFTER == 1 ]]; then
|
||||
@@ -2279,6 +2333,13 @@ URLS=$(jq -c --arg ip "$IP" '
|
||||
}]
|
||||
else [] end
|
||||
' "$TEMPLATE_FILE")
|
||||
# An application with no web address is reached on the ports of its container.
|
||||
SERVICE_ADDRESSES=$(jq -c --arg ip "$IP" '
|
||||
if $ip == "" or ((.first_run.endpoints? // []) | length) > 0 or .catalog_ui.launch.port != null then []
|
||||
else [.container_contract.ports[]?
|
||||
| $ip + ":" + (.container_port | tostring) + (if .protocol == "udp" then "/udp" else "" end)]
|
||||
end
|
||||
' "$TEMPLATE_FILE")
|
||||
if [[ -n $HAOS_URLS ]]; then
|
||||
URLS=$HAOS_URLS
|
||||
elif [[ $HAOS_HEALTHCHECK != 0 ]]; then
|
||||
@@ -2293,7 +2354,7 @@ elif [[ $START_AFTER == 1 ]]; then
|
||||
check_native_device_permissions
|
||||
fi
|
||||
if [[ -z ${PROXMENUX_OCI_TRANSACTION:-} ]] && ! oci_quiet python3 "${SCRIPT_DIR}/oci_instances.py" complete "$VMID" \
|
||||
--archive "$ARCHIVE_PATH" --digest "$DIGEST"; then
|
||||
--archive "$ARCHIVE_PATH" --digest "$DIGEST" ${OWN_ADDRESS:+--address "$OWN_ADDRESS"}; then
|
||||
msg_warn "$(translate "Container installed, but without a verifiable record for future updates.")"
|
||||
fi
|
||||
# The template's notes, then those of this installation's choices, such as the
|
||||
@@ -2309,9 +2370,10 @@ RESULT=$(jq -cn \
|
||||
--arg image_created "$CREATED" \
|
||||
--arg ip "$IP" \
|
||||
--argjson urls "$URLS" \
|
||||
--argjson service_addresses "$SERVICE_ADDRESSES" \
|
||||
--argjson credentials "$CREDENTIALS" \
|
||||
--argjson completion_notes "$COMPLETION_NOTES" \
|
||||
--argjson vmid "$VMID" \
|
||||
--arg log "${OCI_LOG:-}" \
|
||||
'{app_id:$app_id,vmid:$vmid,image:$image,digest:$digest,architecture:$architecture,image_version:$image_version,image_created:$image_created,ip:$ip,urls:$urls,credentials:$credentials,completion_notes:$completion_notes,log:$log}')
|
||||
'{app_id:$app_id,vmid:$vmid,image:$image,digest:$digest,architecture:$architecture,image_version:$image_version,image_created:$image_created,ip:$ip,urls:$urls,service_addresses:$service_addresses,credentials:$credentials,completion_notes:$completion_notes,log:$log}')
|
||||
printf 'PROXMENUX_RESULT=%s\n' "$(printf '%s' "$RESULT" | base64 | tr -d '\n')"
|
||||
|
||||
@@ -87,6 +87,13 @@ def render(template, digest, instance_id, ip=''):
|
||||
item = f'🌐 {link(str(endpoint.get("label") or "Web UI"), url)}: {link(url, url)}'
|
||||
if item:
|
||||
access.append(item)
|
||||
if not access:
|
||||
# With no web address, the application is reached on its ports.
|
||||
for item in (template.get('container_contract') or {}).get('ports') or []:
|
||||
port = item.get('container_port')
|
||||
if isinstance(port, int) and 1 <= port <= 65535:
|
||||
protocol = '/udp' if item.get('protocol') == 'udp' else ''
|
||||
access.append(f'🔌 <code>{address}:{port}{protocol}</code>')
|
||||
badges = (
|
||||
('Docs', DOCS, 'https://img.shields.io/badge/%F0%9F%93%9A_Docs-blue'),
|
||||
('Code', CODE, 'https://img.shields.io/badge/%F0%9F%92%BB_Code-green'),
|
||||
|
||||
@@ -24,6 +24,8 @@ from oci_ui import translate, msg_error, msg_ok
|
||||
|
||||
ROOT = Path('/usr/local/share/proxmenux/oci/instances')
|
||||
MARKER = 'proxmenux-instance='
|
||||
# Stands for the address of the container until the installation knows it.
|
||||
ADDRESS_MARK = '@CONTAINER_ADDRESS@'
|
||||
ACTIVE = {'installing', 'assembling', 'updating', 'recovering'}
|
||||
|
||||
|
||||
@@ -160,8 +162,13 @@ def observe(vmid, installation_id, archive, digest, image=None):
|
||||
return observed
|
||||
|
||||
|
||||
def finish(root, vmid, archive, digest):
|
||||
def finish(root, vmid, archive, digest, address=None):
|
||||
value = read(root, vmid)
|
||||
if address:
|
||||
# The address the installation gave the application is recorded as the
|
||||
# value it is, the way one typed by the user would be.
|
||||
for item in value['deployment'].get('environment', []):
|
||||
item['value'] = item['value'].replace(ADDRESS_MARK, address)
|
||||
observed = observe(vmid, value['installation_id'], archive, digest)
|
||||
value.update(status='assembling' if value['deployment'].get('stack_managed') else 'installed',
|
||||
completed_at=now(), observed=observed)
|
||||
@@ -356,6 +363,7 @@ def main():
|
||||
if action == 'complete':
|
||||
p.add_argument('--archive', required=True)
|
||||
p.add_argument('--digest', required=True)
|
||||
p.add_argument('--address')
|
||||
sub.add_parser('reconcile')
|
||||
resume = sub.add_parser('resume-stack-recording')
|
||||
resume.add_argument('vmid', type=int)
|
||||
@@ -372,7 +380,7 @@ def main():
|
||||
json.loads(Path(args.deployment).read_text()))
|
||||
print(value['installation_id'])
|
||||
elif args.action == 'complete':
|
||||
finish(args.root, args.vmid, args.archive, args.digest)
|
||||
finish(args.root, args.vmid, args.archive, args.digest, args.address)
|
||||
elif args.action == 'failed':
|
||||
value = read(args.root, args.vmid)
|
||||
value.update(status='failed', failed_at=now())
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
"example": {"type": ["string", "null"]},
|
||||
"prompt": {"type": "string", "minLength": 1},
|
||||
"prompt_user": {"type": "boolean"},
|
||||
"container_address": {"type": "string", "pattern": "@CONTAINER_ADDRESS@"},
|
||||
"required": {"type": "boolean"},
|
||||
"sensitive": {"type": "boolean"},
|
||||
"source": {"type": "string"}
|
||||
|
||||
@@ -17,7 +17,9 @@ from .converter import ConversionError
|
||||
from .github_source import SourceError
|
||||
from .i18n import N_, source_text, translate
|
||||
from .installer import (
|
||||
ADDRESS_SHOWN,
|
||||
ADVANCED_MODE,
|
||||
CONTAINER_ADDRESS,
|
||||
DEFAULT_MODE,
|
||||
InstallError,
|
||||
build_deployment,
|
||||
@@ -345,7 +347,8 @@ def _deployment_summary_text(template: dict[str, Any], deployment: dict[str, Any
|
||||
translate("unprivileged LXC") if security.get("unprivileged") else translate("privileged LXC"))
|
||||
environment = plan.get("environment", [])
|
||||
if environment:
|
||||
row(translate("Variables"), ", ".join(f"{item['name']}={item['value']}" for item in environment))
|
||||
row(translate("Variables"), ", ".join(
|
||||
f"{item['name']}={item['value'].replace(CONTAINER_ADDRESS, ADDRESS_SHOWN)}" for item in environment))
|
||||
if plan.get("suite_arr"):
|
||||
lines += ["", *[translate(note) for note in plan.get("completion_notes", [])]]
|
||||
lines.append("")
|
||||
@@ -367,6 +370,8 @@ def _print_installation_summary(result: dict[str, Any], images_removed: str | No
|
||||
console.msg_ok(f"{translate(STACK_LABELS.get(name, name))}: CT {vmid}")
|
||||
for item in result.get("urls") or []:
|
||||
console.msg_ok(f"{translate(item['label'])}: {item['url']}")
|
||||
for address in result.get("service_addresses") or []:
|
||||
console.msg_ok(f"{translate('Service:')} {address}")
|
||||
for item in result.get("credentials") or []:
|
||||
console.msg_info2(translate(item["label"]))
|
||||
username = str(item["username"])
|
||||
|
||||
@@ -31,6 +31,20 @@ DEFAULT_MODE = "default"
|
||||
ADVANCED_MODE = "advanced"
|
||||
|
||||
|
||||
# Stands for the address of the container in a variable that holds the
|
||||
# application's own address, until the installation knows it.
|
||||
CONTAINER_ADDRESS = "@CONTAINER_ADDRESS@"
|
||||
ADDRESS_SHOWN = "<IP>"
|
||||
|
||||
|
||||
def own_address_value(pattern: str, ipv4: str) -> str:
|
||||
"""The application's own address for a container with this network: the
|
||||
fixed address when it has one, its mark when DHCP is still to give it."""
|
||||
if ipv4 in ("dhcp", "host"):
|
||||
return pattern
|
||||
return pattern.replace(CONTAINER_ADDRESS, ipv4.split("/", 1)[0])
|
||||
|
||||
|
||||
def _example_default(name: str, example: str | None, timezone: str | None = None) -> str | None:
|
||||
defaults = {"PUID": "1000", "PGID": "1000", "TZ": timezone or host.timezone()}
|
||||
if name in defaults:
|
||||
@@ -584,6 +598,15 @@ def build_deployment(
|
||||
label = f"{translate('Value for')} {name}"
|
||||
default = _example_default(name, item["example"], timezone)
|
||||
generated = installer_profile.get("generated_sensitive_environment", {}).get(name)
|
||||
if item.get("container_address"):
|
||||
# The address of the container is known or will be: nothing to ask.
|
||||
# The advanced installation shows it, for a name or a proxy in front.
|
||||
value = own_address_value(item["container_address"], ipv4)
|
||||
if advanced:
|
||||
shown = ui.ask(label, value.replace(CONTAINER_ADDRESS, ADDRESS_SHOWN), required=True)
|
||||
value = shown.replace(ADDRESS_SHOWN, CONTAINER_ADDRESS)
|
||||
environment.append({"name": name, "value": value, "sensitive": False})
|
||||
continue
|
||||
if item.get("prompt_user", True) is False:
|
||||
if default in (None, "") and item["required"]:
|
||||
raise InstallError(f"{name}: {translate('no declarative value')}")
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"""An application that has to be told its own address is not asked for it: the
|
||||
container has one, fixed or given by DHCP, and the installation uses that."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
sys.path.insert(0, str(ROOT / "remote"))
|
||||
|
||||
from proxmenux_oci.catalog import Catalog
|
||||
from proxmenux_oci.installer import CONTAINER_ADDRESS, own_address_value
|
||||
import oci_instances
|
||||
|
||||
# Application, its variable and the value it takes at 192.168.1.50.
|
||||
OWN_ADDRESS = {
|
||||
"bookstack": ("APP_URL", "http://192.168.1.50"),
|
||||
"healthchecks": ("SITE_ROOT", "http://192.168.1.50:8000"),
|
||||
"speedtest-tracker": ("APP_URL", "http://192.168.1.50"),
|
||||
"netbox": ("ALLOWED_HOST", "192.168.1.50"),
|
||||
}
|
||||
|
||||
|
||||
class OwnAddressTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
catalog = Catalog(ROOT)
|
||||
cls.variables = {}
|
||||
for app, (name, _) in OWN_ADDRESS.items():
|
||||
environment = catalog.compose(app)["container_contract"]["environment"]
|
||||
cls.variables[app] = next(item for item in environment if item["name"] == name)
|
||||
|
||||
def test_a_fixed_address_is_used_as_it_is(self):
|
||||
for app, (_, expected) in OWN_ADDRESS.items():
|
||||
with self.subTest(app=app):
|
||||
pattern = self.variables[app]["container_address"]
|
||||
self.assertEqual(own_address_value(pattern, "192.168.1.50/24"), expected)
|
||||
|
||||
def test_dhcp_leaves_the_mark_for_the_installation(self):
|
||||
for app in OWN_ADDRESS:
|
||||
with self.subTest(app=app):
|
||||
pattern = self.variables[app]["container_address"]
|
||||
self.assertIn(CONTAINER_ADDRESS, own_address_value(pattern, "dhcp"))
|
||||
|
||||
def test_the_address_is_that_of_the_port_the_application_listens_on(self):
|
||||
catalog = Catalog(ROOT)
|
||||
for app, (_, expected) in OWN_ADDRESS.items():
|
||||
with self.subTest(app=app):
|
||||
endpoint = catalog.compose(app)["first_run"]["endpoints"][0]
|
||||
if not expected.startswith("http"):
|
||||
continue # A bare address names the machine, not a port.
|
||||
port = "" if endpoint["port"] == 80 else f":{endpoint['port']}"
|
||||
self.assertEqual(expected, f"{endpoint['scheme']}://192.168.1.50{port}")
|
||||
|
||||
def test_the_installer_resolves_the_mark_before_the_first_start(self):
|
||||
script = (ROOT / "remote/install_oci.sh").read_text()
|
||||
self.assertIn(f"OWN_ADDRESS_MARK='{CONTAINER_ADDRESS}'", script)
|
||||
self.assertLess(script.index("\napply_own_address\n"), script.index('msg_info "$(translate "Starting the container...")"'))
|
||||
self.assertEqual(oci_instances.ADDRESS_MARK, CONTAINER_ADDRESS)
|
||||
|
||||
def test_the_record_keeps_the_address_and_not_the_mark(self):
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
root = Path(folder)
|
||||
record = {"schema_version": 1, "vmid": 900, "installation_id": "00000000-0000-4000-8000-000000000000", "status": "installing",
|
||||
"deployment": {"environment": [{"name": "APP_URL", "value": f"http://{CONTAINER_ADDRESS}"},
|
||||
{"name": "TZ", "value": "Europe/Madrid"}]}}
|
||||
path = oci_instances.location(root, 900)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(record))
|
||||
observe, oci_instances.observe = oci_instances.observe, lambda *args, **kwargs: {"config": ""}
|
||||
try:
|
||||
oci_instances.finish(root, 900, "archive", "digest", "192.168.1.50")
|
||||
finally:
|
||||
oci_instances.observe = observe
|
||||
saved = json.loads(path.read_text())["deployment"]["environment"]
|
||||
self.assertEqual(saved[0]["value"], "http://192.168.1.50")
|
||||
self.assertEqual(saved[1]["value"], "Europe/Madrid")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,78 @@
|
||||
"""A port that does not speak HTTP is not announced as a web address: the link
|
||||
would open nothing, and an update would wait on it for an answer that never
|
||||
comes and undo itself."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
sys.path.insert(0, str(ROOT / "remote"))
|
||||
|
||||
from proxmenux_oci.catalog import Catalog
|
||||
import oci_description
|
||||
import oci_instance_transaction
|
||||
|
||||
# Application and the port its recipe was generated with as web address.
|
||||
SERVICES = {
|
||||
"mariadb": 3306,
|
||||
"ngircd": 6667,
|
||||
"openssh-server": 2222,
|
||||
"syslog-ng": 6601,
|
||||
"faster-whisper": 10300,
|
||||
"piper": 10200,
|
||||
}
|
||||
|
||||
|
||||
class ServicePortTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
catalog = Catalog(ROOT)
|
||||
cls.templates = {app: catalog.compose(app) for app in SERVICES}
|
||||
|
||||
def test_no_web_address_is_announced(self):
|
||||
for app, template in self.templates.items():
|
||||
with self.subTest(app=app):
|
||||
self.assertEqual(template["first_run"]["endpoints"], [])
|
||||
self.assertIsNone(template["catalog_ui"]["launch"]["port"])
|
||||
|
||||
def test_the_port_is_still_the_one_of_the_container(self):
|
||||
for app, port in SERVICES.items():
|
||||
with self.subTest(app=app):
|
||||
ports = [item["container_port"] for item in self.templates[app]["container_contract"]["ports"]]
|
||||
self.assertIn(port, ports)
|
||||
|
||||
def test_an_update_checks_that_the_container_keeps_running(self):
|
||||
for app, template in self.templates.items():
|
||||
with self.subTest(app=app):
|
||||
self.assertEqual(oci_instance_transaction.effective_healthcheck(template)["type"], "running")
|
||||
|
||||
def test_the_notes_give_the_address_as_text(self):
|
||||
notes = oci_description.render(self.templates["mariadb"], "sha256:" + "0" * 64, "instance", "192.168.1.50")
|
||||
self.assertIn("<code>192.168.1.50:3306</code>", notes)
|
||||
self.assertNotIn("Web UI", notes)
|
||||
self.assertNotIn("http://192.168.1.50", notes)
|
||||
notes = oci_description.render(self.templates["syslog-ng"], "sha256:" + "0" * 64, "instance", "192.168.1.50")
|
||||
self.assertIn("<code>192.168.1.50:5514/udp</code>", notes)
|
||||
|
||||
def test_a_web_application_keeps_its_link_in_the_notes(self):
|
||||
template = Catalog(ROOT).compose("bookstack")
|
||||
notes = oci_description.render(template, "sha256:" + "0" * 64, "instance", "192.168.1.50")
|
||||
self.assertIn("http://192.168.1.50:80/", notes)
|
||||
self.assertNotIn("<code>192.168.1.50:", notes)
|
||||
|
||||
def test_the_summary_gives_the_address_of_a_service_only(self):
|
||||
script = (ROOT / "remote/install_oci.sh").read_text()
|
||||
self.assertIn("service_addresses:$service_addresses", script)
|
||||
cli = (ROOT / "src/proxmenux_oci/cli.py").read_text()
|
||||
self.assertIn("translate('Service:')", cli)
|
||||
|
||||
def test_mariadb_says_how_to_connect(self):
|
||||
notes = self.templates["mariadb"]["proxmox"]["installer_profile"]["completion_notes"]
|
||||
self.assertTrue(any("port 3306" in note for note in notes))
|
||||
self.assertTrue(any("without a password" in note for note in notes))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user