From debee2e0cfad3a356e31e05ad820116d662d135d Mon Sep 17 00:00:00 2001 From: Oleksander Piskun Date: Tue, 14 Apr 2026 11:45:06 +0000 Subject: [PATCH 1/3] fix(k8s): publish host aliases to coredns-custom instead of patching Corefile Signed-off-by: Oleksander Piskun --- haproxy_agent.py | 116 +++++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/haproxy_agent.py b/haproxy_agent.py index 82d9141..94d9f25 100644 --- a/haproxy_agent.py +++ b/haproxy_agent.py @@ -2071,7 +2071,15 @@ def _k8s_parse_host_aliases() -> list[dict[str, Any]]: async def _k8s_ensure_coredns_host_aliases() -> None: - """Patch CoreDNS to resolve HP_K8S_HOST_ALIASES cluster-wide.""" + """Publish HP_K8S_HOST_ALIASES into the 'coredns-custom' ConfigMap so they resolve cluster-wide. + + Uses k3s's /etc/coredns/custom/*.server import convention (also honoured by any CoreDNS + Deployment that mounts a 'coredns-custom' ConfigMap with optional=true). The main + 'coredns' ConfigMap managed by the distribution operator is never touched; the 'reload' + plugin present in the stock Corefile picks up the new file within seconds, so no + Deployment restart is needed either. The ConfigMap key is prefixed 'harp-' so other + operators writing unrelated keys into 'coredns-custom' are not disturbed. + """ if not K8S_ENABLED or not K8S_HOST_ALIASES_RAW.strip(): return @@ -2079,71 +2087,79 @@ async def _k8s_ensure_coredns_host_aliases() -> None: if not host_aliases: return - LOGGER.info("Ensuring CoreDNS resolves host aliases: %s", K8S_HOST_ALIASES_RAW) + # Build one standalone Corefile server block per zone. Entries share a single block + # to keep the file compact and to avoid plugin-instance conflicts between zones. + hosts_entries: list[str] = [] + zone_names: list[str] = [] + for alias in host_aliases: + for hostname in alias["hostnames"]: + hosts_entries.append(f" {alias['ip']} {hostname}") + zone_names.append(f"{hostname}:53") + + server_file = ( + f"{' '.join(zone_names)} {{\n" + " hosts {\n" + f"{chr(10).join(hosts_entries)}\n" + " fallthrough\n" + " }\n" + " forward . /etc/resolv.conf\n" + "}\n" + ) - try: - status, data, _text = await _k8s_request( - "GET", "/api/v1/namespaces/kube-system/configmaps/coredns", - ) - if status != 200 or not data: - LOGGER.warning("Could not read CoreDNS ConfigMap (HTTP %d), skipping.", status) - return + cm_path = "/api/v1/namespaces/kube-system/configmaps/coredns-custom" + key = "harp-host-aliases.server" - corefile = data.get("data", {}).get("Corefile", "") - if not corefile: - LOGGER.warning("CoreDNS ConfigMap has no Corefile entry, skipping.") - return + LOGGER.info("Publishing %d host alias zone(s) into coredns-custom: %s", len(zone_names), K8S_HOST_ALIASES_RAW) - hosts_lines: list[str] = [] - for alias in host_aliases: - for hostname in alias["hostnames"]: - hosts_lines.append(f" {alias['ip']} {hostname}") - hosts_block = "hosts {\n" + "\n".join(hosts_lines) + "\n fallthrough\n }" - - hosts_re = re.compile(r"hosts\s*\{[^}]*\}") - if hosts_re.search(corefile): - new_corefile = hosts_re.sub(hosts_block, corefile, count=1) - elif "forward ." in corefile: - new_corefile = corefile.replace("forward .", f"{hosts_block}\n forward .", 1) - else: - LOGGER.warning( - "CoreDNS Corefile has no 'hosts' block and no 'forward' directive, cannot patch." + try: + status, existing, _text = await _k8s_request("GET", cm_path) + + if status == 404: + configmap = { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "coredns-custom", + "namespace": "kube-system", + "labels": {"app.kubernetes.io/managed-by": "harp"}, + }, + "data": {key: server_file}, + } + status, _, _text = await _k8s_request( + "POST", + "/api/v1/namespaces/kube-system/configmaps", + json_body=configmap, ) + if status in (200, 201): + LOGGER.info("Created coredns-custom ConfigMap with HaRP host aliases.") + else: + LOGGER.warning("Failed to create coredns-custom (HTTP %d): %s", status, _text[:200]) return - if new_corefile == corefile: - LOGGER.info("CoreDNS already has correct host aliases, no patch needed.") + if status != 200 or not isinstance(existing, dict): + LOGGER.warning("Could not read coredns-custom (HTTP %d), skipping.", status) return - status, _, _text = await _k8s_request( - "PATCH", - "/api/v1/namespaces/kube-system/configmaps/coredns", - json_body={"data": {"Corefile": new_corefile}}, - content_type="application/strategic-merge-patch+json", - ) - if status != 200: - LOGGER.warning("Failed to patch CoreDNS ConfigMap (HTTP %d): %s", status, _text[:200]) + current = (existing.get("data") or {}).get(key) + if current == server_file: + LOGGER.info("coredns-custom already carries the expected HaRP host aliases, no patch needed.") return - restart_annotation = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + # Strategic merge patch on ConfigMap.data merges keys individually — unrelated keys + # written by other operators stay intact. status, _, _text = await _k8s_request( "PATCH", - "/apis/apps/v1/namespaces/kube-system/deployments/coredns", - json_body={ - "spec": {"template": {"metadata": {"annotations": { - "harp.nextcloud.com/restartedAt": restart_annotation, - }}}} - }, + cm_path, + json_body={"data": {key: server_file}}, content_type="application/strategic-merge-patch+json", ) - if status != 200: - LOGGER.warning("Failed to restart CoreDNS Deployment (HTTP %d): %s", status, _text[:200]) - return - - LOGGER.info("CoreDNS patched and restarted with host aliases: %s", K8S_HOST_ALIASES_RAW) + if status == 200: + LOGGER.info("Updated coredns-custom with HaRP host aliases.") + else: + LOGGER.warning("Failed to patch coredns-custom (HTTP %d): %s", status, _text[:200]) except Exception as exc: - LOGGER.warning("Failed to configure CoreDNS host aliases (non-fatal): %s", exc) + LOGGER.warning("Failed to configure coredns-custom host aliases (non-fatal): %s", exc) def _k8s_build_deployment_manifest(payload: CreateExAppPayload, replicas: int) -> dict[str, Any]: From 4552a05b398dc88b44e01684d349d414b141dbaa Mon Sep 17 00:00:00 2001 From: Oleksander Piskun Date: Tue, 14 Apr 2026 11:45:11 +0000 Subject: [PATCH 2/3] ci: add AppAPI deploy workflows (Docker and K8s) against HaRP built from PR Signed-off-by: Oleksander Piskun --- .github/workflows/tests-deploy-docker.yml | 593 ++++++++++++++++++ .../workflows/tests-deploy-k8s-clusterip.yml | 234 +++++++ .../tests-deploy-k8s-loadbalancer.yml | 241 +++++++ .github/workflows/tests-deploy-k8s-manual.yml | 236 +++++++ .github/workflows/tests-deploy-k8s.yml | 234 +++++++ LICENSES/MIT.txt | 18 + 6 files changed, 1556 insertions(+) create mode 100644 .github/workflows/tests-deploy-docker.yml create mode 100644 .github/workflows/tests-deploy-k8s-clusterip.yml create mode 100644 .github/workflows/tests-deploy-k8s-loadbalancer.yml create mode 100644 .github/workflows/tests-deploy-k8s-manual.yml create mode 100644 .github/workflows/tests-deploy-k8s.yml create mode 100644 LICENSES/MIT.txt diff --git a/.github/workflows/tests-deploy-docker.yml b/.github/workflows/tests-deploy-docker.yml new file mode 100644 index 0000000..f301805 --- /dev/null +++ b/.github/workflows/tests-deploy-docker.yml @@ -0,0 +1,593 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# Adapted from nextcloud/app_api's tests-deploy.yml — keeps the four HaRP-related +# jobs (bridge, bridge-no-tls, host, manual-host) so HaRP PRs exercise the same +# Docker deployment matrix AppAPI does, against the HaRP image built from this +# repo instead of the published ghcr.io:latest tag. +name: Tests - Docker Deploy (HaRP) + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: tests-deploy-docker-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + nc-docker-harp-bridge: + runs-on: ubuntu-22.04 + name: NC In Julius Docker (HaRP-Bridge network) + env: + docker-image: ghcr.io/juliusknorr/nextcloud-dev-php83:master + + steps: + - name: Checkout AppAPI + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + repository: nextcloud/app_api + ref: main + path: app_api + + - name: Checkout HaRP (this PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + path: harp + + - name: Build HaRP image (this PR) + working-directory: harp + run: docker build -t ghcr.io/nextcloud/nextcloud-appapi-harp:latest . + + - name: Create containers + run: | + docker network create master_bridge + docker run \ + -e HP_SHARED_KEY="some_very_secure_password" \ + -e NC_INSTANCE_URL="http://nextcloud" \ + -e HP_LOG_LEVEL="debug" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + --net master_bridge --name appapi-harp -h appapi-harp \ + --restart unless-stopped \ + -d ghcr.io/nextcloud/nextcloud-appapi-harp:latest + docker run --net master_bridge --name nextcloud-docker --rm -d ${{ env.docker-image }} + + sed -i 's/127\.0\.0\.1:8080/nextcloud-docker/' app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf + sed -i 's/127\.0\.0\.1:8780/appapi-harp:8780/' app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf + cat app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf + + docker run --net master_bridge --name nextcloud --rm \ + -v $(pwd)/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf:/etc/nginx/conf.d/default.conf:ro \ + -d nginx + + - name: Wait for Nextcloud bootstrap + run: | + for i in $(seq 1 60); do + if docker exec nextcloud-docker test -f /var/www/html/version.php 2>/dev/null; then + echo "Nextcloud source ready after ${i}0s" + break + fi + echo "Waiting for Nextcloud bootstrap... ($i/60)" + sleep 10 + done + docker exec nextcloud-docker test -f /var/www/html/version.php || (echo "Bootstrap timed out" && exit 1) + + - name: Install AppAPI + run: | + docker exec -w /var/www/html/apps nextcloud-docker git clone https://github.com/nextcloud/app_api.git app_api + docker exec -w /var/www/html/apps/app_api nextcloud-docker git checkout main + docker exec nextcloud-docker sudo -u www-data php occ app:enable app_api + docker exec nextcloud-docker sudo -u www-data php occ app_api:daemon:register \ + harp_proxy "Harp Proxy with DSP" "docker-install" "http" "appapi-harp:8780" "http://nextcloud" \ + --harp --harp_frp_address "appapi-harp:8782" --harp_shared_key "some_very_secure_password" \ + --net=master_bridge --set-default + docker exec nextcloud-docker sudo -u www-data php occ app_api:daemon:list + docker exec nextcloud-docker ping -c 1 appapi-harp + docker exec nextcloud-docker ping -c 1 nextcloud + + - name: Registering and enabling Skeleton ExApp + run: | + docker exec nextcloud-docker sudo -u www-data php occ app_api:app:register app-skeleton-python harp_proxy \ + --info-xml https://raw.githubusercontent.com/nextcloud/app-skeleton-python/main/appinfo/info.xml \ + --wait-finish + + - name: Docker inspect output + if: always() + run: docker inspect nc_app_app-skeleton-python + + - name: Disable ExApp + run: | + docker exec nextcloud-docker sudo -u www-data php occ app_api:app:disable app-skeleton-python + + - name: Copy NC log to host + if: always() + run: docker cp nextcloud-docker:/var/www/html/data/nextcloud.log nextcloud.log + + - name: Check logs + run: | + grep -q 'Hello from app-skeleton-python :)' nextcloud.log || error + grep -q 'Bye bye from app-skeleton-python :(' nextcloud.log || error + + - name: Save app and HaRP container info & logs + if: always() + run: | + docker inspect appapi-harp | json_pp > harp_bridge_container.json + docker logs appapi-harp > harp_bridge_container.log 2>&1 + docker inspect nc_app_app-skeleton-python | json_pp > app_harp_bridge_container.json + docker logs nc_app_app-skeleton-python > app_harp_bridge_container.log 2>&1 + docker logs nextcloud > nginx.log 2>&1 + + - name: Unregister Skeleton & Daemon + run: | + docker exec nextcloud-docker sudo -u www-data php occ app_api:app:unregister app-skeleton-python + docker exec nextcloud-docker sudo -u www-data php occ app_api:daemon:unregister harp_proxy + + - name: Show all logs + if: always() + run: tail -v -n +1 *container.json *.log + + nc-docker-harp-bridge-no-tls: + runs-on: ubuntu-22.04 + name: NC In Julius Docker (HaRP-Bridge network-no FRP TLS) + env: + docker-image: ghcr.io/juliusknorr/nextcloud-dev-php83:master + + steps: + - name: Checkout AppAPI + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + repository: nextcloud/app_api + ref: main + path: app_api + + - name: Checkout HaRP (this PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + path: harp + + - name: Build HaRP image (this PR) + working-directory: harp + run: docker build -t ghcr.io/nextcloud/nextcloud-appapi-harp:latest . + + - name: Create containers + run: | + docker network create master_bridge + docker run \ + -e HP_SHARED_KEY="some_very_secure_password" \ + -e HP_FRP_DISABLE_TLS="true" \ + -e NC_INSTANCE_URL="http://nextcloud" \ + -e HP_LOG_LEVEL="debug" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + --net master_bridge --name appapi-harp -h appapi-harp \ + --restart unless-stopped \ + -d ghcr.io/nextcloud/nextcloud-appapi-harp:latest + docker run --net master_bridge --name nextcloud-docker --rm -d ${{ env.docker-image }} + + sed -i 's/127\.0\.0\.1:8080/nextcloud-docker/' app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf + sed -i 's/127\.0\.0\.1:8780/appapi-harp:8780/' app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf + cat app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf + + docker run --net master_bridge --name nextcloud --rm \ + -v $(pwd)/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf:/etc/nginx/conf.d/default.conf:ro \ + -d nginx + + - name: Wait for Nextcloud bootstrap + run: | + for i in $(seq 1 60); do + if docker exec nextcloud-docker test -f /var/www/html/version.php 2>/dev/null; then + echo "Nextcloud source ready after ${i}0s" + break + fi + echo "Waiting for Nextcloud bootstrap... ($i/60)" + sleep 10 + done + docker exec nextcloud-docker test -f /var/www/html/version.php || (echo "Bootstrap timed out" && exit 1) + + - name: Install AppAPI + run: | + docker exec -w /var/www/html/apps nextcloud-docker git clone https://github.com/nextcloud/app_api.git app_api + docker exec -w /var/www/html/apps/app_api nextcloud-docker git checkout main + docker exec nextcloud-docker sudo -u www-data php occ app:enable app_api + docker exec nextcloud-docker sudo -u www-data php occ app_api:daemon:register \ + harp_proxy "Harp Proxy with DSP" "docker-install" "http" "appapi-harp:8780" "http://nextcloud" \ + --harp --harp_frp_address "appapi-harp:8782" --harp_shared_key "some_very_secure_password" \ + --net=master_bridge --set-default + docker exec nextcloud-docker sudo -u www-data php occ app_api:daemon:list + docker exec nextcloud-docker ping -c 1 appapi-harp + docker exec nextcloud-docker ping -c 1 nextcloud + + - name: Registering and enabling Skeleton ExApp + run: | + docker exec nextcloud-docker sudo -u www-data php occ app_api:app:register app-skeleton-python harp_proxy \ + --info-xml https://raw.githubusercontent.com/nextcloud/app-skeleton-python/main/appinfo/info.xml \ + --wait-finish + + - name: Docker inspect output + if: always() + run: docker inspect nc_app_app-skeleton-python + + - name: Disable ExApp + run: | + docker exec nextcloud-docker sudo -u www-data php occ app_api:app:disable app-skeleton-python + + - name: Copy NC log to host + if: always() + run: docker cp nextcloud-docker:/var/www/html/data/nextcloud.log nextcloud.log + + - name: Check logs + run: | + grep -q 'Hello from app-skeleton-python :)' nextcloud.log || error + grep -q 'Bye bye from app-skeleton-python :(' nextcloud.log || error + + - name: Save app and HaRP container info & logs + if: always() + run: | + docker inspect appapi-harp | json_pp > harp_bridge_no_tls_container.json + docker logs appapi-harp > harp_bridge_no_tls_container.log 2>&1 + docker inspect nc_app_app-skeleton-python | json_pp > app_harp_bridge_no_tls_container.json + docker logs nc_app_app-skeleton-python > app_harp_bridge_no_tls_container.log 2>&1 + + - name: Unregister Skeleton & Daemon + run: | + docker exec nextcloud-docker sudo -u www-data php occ app_api:app:unregister app-skeleton-python + docker exec nextcloud-docker sudo -u www-data php occ app_api:daemon:unregister harp_proxy + + - name: Show all logs + if: always() + run: tail -v -n +1 *container.json *.log + + nc-host-harp-host: + runs-on: ubuntu-22.04 + name: NC In Host (HaRP-Host network) + + services: + postgres: + image: ghcr.io/nextcloud/continuous-integration-postgres-14:latest # zizmor: ignore[unpinned-images] + ports: + - 4444:5432/tcp + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: rootpassword + POSTGRES_DB: nextcloud + options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 + + steps: + - name: Checkout server + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + submodules: true + repository: nextcloud/server + ref: master + + - name: Checkout AppAPI + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + repository: nextcloud/app_api + ref: main + path: apps/app_api + + - name: Checkout HaRP (this PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + path: harp + + - name: Set up php 8.3 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 + with: + php-version: 8.3 + extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql, redis + coverage: none + ini-file: development + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check composer file existence + id: check_composer + uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v2 + with: + files: apps/app_api/composer.json + + - name: Set up dependencies + if: steps.check_composer.outputs.files_exists == 'true' + working-directory: apps/app_api + run: composer i + + - name: Set up Nextcloud + env: + DB_PORT: 4444 + run: | + mkdir data + ./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 \ + --database-port=$DB_PORT --database-user=root --database-pass=rootpassword \ + --admin-user admin --admin-pass admin + ./occ config:system:set loglevel --value=0 --type=integer + ./occ config:system:set debug --value=true --type=boolean + ./occ config:system:set overwrite.cli.url --value http://127.0.0.1 --type=string + ./occ app:enable --force app_api + + - name: Build HaRP image (this PR) + working-directory: harp + run: docker build -t ghcr.io/nextcloud/nextcloud-appapi-harp:latest . + + - name: Create HaRP container + run: | + cat apps/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf + docker run \ + -e HP_SHARED_KEY="some_very_secure_password" \ + -e NC_INSTANCE_URL="http://127.0.0.1" \ + -e HP_LOG_LEVEL="debug" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + --net host --name appapi-harp \ + --restart unless-stopped \ + -d ghcr.io/nextcloud/nextcloud-appapi-harp:latest + docker run --net host --name nextcloud --rm \ + -v $(pwd)/apps/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf:/etc/nginx/conf.d/default.conf:ro \ + -d nginx + + - name: Test deploy + run: | + PHP_CLI_SERVER_WORKERS=2 php -S 127.0.0.1:8080 & + ./occ app_api:daemon:register \ + harp_proxy "Harp Proxy with DSP" "docker-install" "http" "127.0.0.1:8780" "http://127.0.0.1" \ + --harp --harp_frp_address "127.0.0.1:8782" --harp_shared_key "some_very_secure_password" \ + --net host --set-default + ./occ app_api:daemon:list + + ./occ app_api:app:register app-skeleton-python harp_proxy \ + --info-xml https://raw.githubusercontent.com/nextcloud/app-skeleton-python/main/appinfo/info.xml \ + --wait-finish + ./occ app_api:app:disable app-skeleton-python + + - name: Check logs + run: | + grep -q 'Hello from app-skeleton-python :)' data/nextcloud.log || error + grep -q 'Bye bye from app-skeleton-python :(' data/nextcloud.log || error + + - name: Save app and HaRP container info & logs + if: always() + run: | + docker inspect appapi-harp | json_pp > harp_host_container.json + docker logs appapi-harp > harp_host_container.log 2>&1 + docker inspect nc_app_app-skeleton-python | json_pp > app_host_container.json + docker logs nc_app_app-skeleton-python > app_host_container.log 2>&1 + + - name: Unregister Skeleton & Daemon + run: | + ./occ app_api:app:unregister app-skeleton-python + ./occ app_api:daemon:unregister harp_proxy + + - name: Test OCC commands(docker) + run: python3 apps/app_api/tests/test_occ_commands_docker.py + + - name: Show all logs + if: always() + run: tail -v -n +1 *container.json *.log data/nextcloud.log + + nc-host-manual-harp-host: + runs-on: ubuntu-22.04 + name: NC In Host (Manual HaRP-Host network) + + services: + postgres: + image: ghcr.io/nextcloud/continuous-integration-postgres-14:latest # zizmor: ignore[unpinned-images] + ports: + - 4444:5432/tcp + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: rootpassword + POSTGRES_DB: nextcloud + options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 + + steps: + - name: Checkout server + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + submodules: true + repository: nextcloud/server + ref: master + + - name: Checkout AppAPI + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + repository: nextcloud/app_api + ref: main + path: apps/app_api + + - name: Checkout HaRP (this PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + path: harp + + - name: Set up php 8.3 + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 + with: + php-version: 8.3 + extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql, redis + coverage: none + ini-file: development + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check composer file existence + id: check_composer + uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v2 + with: + files: apps/app_api/composer.json + + - name: Set up dependencies + if: steps.check_composer.outputs.files_exists == 'true' + working-directory: apps/app_api + run: composer i + + - name: Set up Nextcloud + env: + DB_PORT: 4444 + OC_PASS: password + run: | + mkdir data + ./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 \ + --database-port=$DB_PORT --database-user=root --database-pass=rootpassword \ + --admin-user admin --admin-pass admin + ./occ config:system:set loglevel --value=0 --type=integer + ./occ config:system:set debug --value=true --type=boolean + ./occ config:system:set overwrite.cli.url --value http://127.0.0.1 --type=string + ./occ app:enable --force app_api + ./occ user:add --password-from-env usr + + - name: Build HaRP image (this PR) + working-directory: harp + run: docker build -t ghcr.io/nextcloud/nextcloud-appapi-harp:latest . + + - name: Create HaRP and app container + run: | + cat apps/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf + docker run \ + -e HP_SHARED_KEY="some_very_secure_password" \ + -e NC_INSTANCE_URL="http://127.0.0.1" \ + -e HP_LOG_LEVEL="debug" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + --net host --name appapi-harp \ + --restart unless-stopped \ + -d ghcr.io/nextcloud/nextcloud-appapi-harp:latest + docker run --net host --name nextcloud --rm \ + -v $(pwd)/apps/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf:/etc/nginx/conf.d/default.conf:ro \ + -d nginx + docker run --net host --name nc_app_app-skeleton-python \ + -e AA_VERSION=4.0.0 \ + -e APP_SECRET=12345 \ + -e APP_ID=app-skeleton-python \ + -e APP_DISPLAY_NAME="App Skeleton" \ + -e APP_VERSION=3.0.1 \ + -e APP_HOST=0.0.0.0 \ + -e APP_PORT=23000 \ + -e NEXTCLOUD_URL=http://127.0.0.1 \ + --rm -d ghcr.io/nextcloud/app-skeleton-python:latest + + - name: Test deploy + run: | + set -x + PHP_CLI_SERVER_WORKERS=2 php -S 127.0.0.1:8080 & + ./occ app_api:daemon:register \ + manual_install_harp "Harp Manual Install" "manual-install" "http" "127.0.0.1:8780" "http://127.0.0.1" \ + --net host --harp --harp_frp_address "127.0.0.1:8782" --harp_shared_key "some_very_secure_password" + ./occ app_api:daemon:list + + ./occ app_api:app:register app-skeleton-python manual_install_harp \ + --json-info " \ + { \ + \"id\": \"app-skeleton-python\", \ + \"name\": \"App Skeleton\", \ + \"daemon_config_name\": \"manual_install_harp\", \ + \"version\": \"3.0.1\", \ + \"secret\": \"12345\", \ + \"port\": 23000, \ + \"routes\": [ \ + { \ + \"url\": \"^/public$\", \ + \"verb\": \"GET\", \ + \"access_level\": 0 \ + }, \ + { \ + \"url\": \"^/user$\", \ + \"verb\": \"GET\", \ + \"access_level\": 1 \ + }, \ + { \ + \"url\": \"^/admin$\", \ + \"verb\": \"GET\", \ + \"access_level\": 2 \ + }, \ + { \ + \"url\": \"^/$\", \ + \"verb\": \"GET\", \ + \"access_level\": 1 \ + }, \ + { \ + \"url\": \"^/ws$\", \ + \"verb\": \"GET\", \ + \"access_level\": 1 \ + } \ + ] \ + }" \ + --wait-finish + + function test_req() { + temp=$(mktemp) + curl -i -o $temp -w "%{http_code}" $2 http://127.0.0.1/exapps/app-skeleton-python/$1 | grep -q "$3" || { + echo "$1 :: $2 :: $3" + cat $temp + error + } + echo -e "\n\n-------------------------\n$1 :: $2 :: $3\n" | tee -a curl.log + cat $temp | tee -a curl.log + } + + test_req public "--" 200 + test_req public "-u usr:password" 200 + test_req public "-u admin:admin" 200 + + test_req user "--" 403 + test_req user "-u usr:password" 200 + test_req user "-u admin:admin" 200 + + test_req admin "--" 403 + test_req admin "-u usr:password" 403 + test_req admin "-u admin:admin" 200 + + # clients get blacklisted for 5 bad requests for 5 minutes + sleep 301 + + test_req imaginary "--" 404 + test_req imaginary "-u usr:password" 404 + test_req imaginary "-u admin:admin" 404 + + ./occ app_api:app:disable app-skeleton-python + + - name: Check logs + run: | + grep -q 'Hello from app-skeleton-python :)' data/nextcloud.log || error + grep -q 'Bye bye from app-skeleton-python :(' data/nextcloud.log || error + + - name: Save app and HaRP container info & logs + if: always() + run: | + docker inspect appapi-harp | json_pp > harp_host_container.json + docker logs appapi-harp > harp_host_container.log 2>&1 + docker inspect nc_app_app-skeleton-python | json_pp > app_host_container.json + docker logs nc_app_app-skeleton-python > app_host_container.log 2>&1 + + - name: Unregister Skeleton & Daemon + run: | + ./occ app_api:app:unregister app-skeleton-python + ./occ app_api:daemon:unregister manual_install_harp + + - name: Test OCC commands(docker) + run: python3 apps/app_api/tests/test_occ_commands_docker.py + + - name: Show all logs + if: always() + run: tail -v -n +1 *container.json *.log data/nextcloud.log + + tests-deploy-success: + permissions: + contents: none + runs-on: ubuntu-22.04 + needs: [nc-docker-harp-bridge, nc-docker-harp-bridge-no-tls, nc-host-harp-host, nc-host-manual-harp-host] + name: Tests-Deploy-Docker-OK + steps: + - run: echo "Docker (HaRP) deploy tests passed successfully" diff --git a/.github/workflows/tests-deploy-k8s-clusterip.yml b/.github/workflows/tests-deploy-k8s-clusterip.yml new file mode 100644 index 0000000..ad58263 --- /dev/null +++ b/.github/workflows/tests-deploy-k8s-clusterip.yml @@ -0,0 +1,234 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# Adapted from nextcloud/app_api's tests-deploy-k8s.yml so HaRP PRs exercise +# the integration with AppAPI against a freshly built HaRP image. +name: Tests - K8s Deploy (ClusterIP) + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: tests-deploy-k8s-clusterip-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + HP_SHARED_KEY: 'test_shared_key_12345' + +jobs: + k8s-deploy-clusterip: + runs-on: ubuntu-22.04 + name: K8s Deploy Lifecycle (ClusterIP) + + services: + postgres: + image: ghcr.io/nextcloud/continuous-integration-postgres-14:latest # zizmor: ignore[unpinned-images] + ports: + - 4444:5432/tcp + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: rootpassword + POSTGRES_DB: nextcloud + options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 + + steps: + - name: Checkout server + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + submodules: true + repository: nextcloud/server + ref: master + + - name: Checkout AppAPI + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + repository: nextcloud/app_api + ref: main + path: apps/app_api + + - name: Checkout HaRP (this PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + path: harp + + - name: Set up php + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 + with: + php-version: '8.3' + extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql + coverage: none + ini-file: development + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check composer file existence + id: check_composer + uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v2 + with: + files: apps/app_api/composer.json + + - name: Set up dependencies + if: steps.check_composer.outputs.files_exists == 'true' + working-directory: apps/app_api + run: composer i + + - name: Set up Nextcloud + env: + DB_PORT: 4444 + run: | + mkdir data + ./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 \ + --database-port=$DB_PORT --database-user=root --database-pass=rootpassword \ + --admin-user admin --admin-pass admin + ./occ config:system:set loglevel --value=0 --type=integer + ./occ config:system:set debug --value=true --type=boolean + ./occ app:enable --force app_api + + - name: Install k3s + run: | + curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik --disable servicelb" sh - + sudo chmod 644 /etc/rancher/k3s/k3s.yaml + echo "KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> $GITHUB_ENV + + - name: Wait for k3s and create namespace + run: | + kubectl wait --for=condition=Ready node --all --timeout=120s + kubectl create namespace nextcloud-exapps + NODE_IP=$(kubectl get node -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') + echo "NODE_IP=${NODE_IP}" >> $GITHUB_ENV + echo "k3s node IP: $NODE_IP" + + - name: Configure Nextcloud for k3s networking + run: | + ./occ config:system:set overwrite.cli.url --value "http://${{ env.NODE_IP }}" --type=string + ./occ config:system:set trusted_domains 1 --value "${{ env.NODE_IP }}" + + - name: Create K8s service account for HaRP + run: | + kubectl -n nextcloud-exapps create serviceaccount harp-sa + kubectl create clusterrolebinding harp-admin \ + --clusterrole=cluster-admin \ + --serviceaccount=nextcloud-exapps:harp-sa + K3S_TOKEN=$(kubectl -n nextcloud-exapps create token harp-sa --duration=2h) + echo "K3S_TOKEN=${K3S_TOKEN}" >> $GITHUB_ENV + + - name: Pre-pull ExApp image into k3s + run: sudo k3s ctr images pull ghcr.io/nextcloud/app-skeleton-python:latest + + - name: Build HaRP image (this PR) + working-directory: harp + run: docker build -t ghcr.io/nextcloud/nextcloud-appapi-harp:latest . + + - name: Start HaRP with K8s backend + run: | + docker run --net host --name appapi-harp \ + -e HP_SHARED_KEY="${{ env.HP_SHARED_KEY }}" \ + -e NC_INSTANCE_URL="http://${{ env.NODE_IP }}" \ + -e HP_LOG_LEVEL="debug" \ + -e HP_K8S_ENABLED="true" \ + -e HP_K8S_API_SERVER="https://127.0.0.1:6443" \ + -e HP_K8S_BEARER_TOKEN="${{ env.K3S_TOKEN }}" \ + -e HP_K8S_NAMESPACE="nextcloud-exapps" \ + -e HP_K8S_VERIFY_SSL="false" \ + --restart unless-stopped \ + -d ghcr.io/nextcloud/nextcloud-appapi-harp:latest + + - name: Start nginx proxy + run: | + docker run --net host --name nextcloud --rm \ + -v $(pwd)/apps/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf:/etc/nginx/conf.d/default.conf:ro \ + -d nginx + + - name: Start Nextcloud + run: PHP_CLI_SERVER_WORKERS=2 php -S 0.0.0.0:8080 & + + - name: Wait for HaRP K8s readiness + run: | + for i in $(seq 1 30); do + if curl -sf http://${{ env.NODE_IP }}:8780/exapps/app_api/info \ + -H "harp-shared-key: ${{ env.HP_SHARED_KEY }}" 2>/dev/null | grep -q '"kubernetes"'; then + echo "HaRP is ready with K8s backend" + exit 0 + fi + echo "Waiting for HaRP... ($i/30)" + sleep 2 + done + echo "HaRP K8s readiness check failed" + docker logs appapi-harp + exit 1 + + - name: Register K8s daemon (ClusterIP) + run: | + ./occ app_api:daemon:register \ + k8s_test "K8s Test" "kubernetes-install" "http" "${{ env.NODE_IP }}:8780" "http://${{ env.NODE_IP }}" \ + --harp --harp_shared_key "${{ env.HP_SHARED_KEY }}" \ + --k8s --k8s_expose_type=clusterip --set-default + ./occ app_api:daemon:list + + - name: Run K8s integration tests (ClusterIP) + env: + K8S_EXPOSE_TYPE: clusterip + run: python3 apps/app_api/tests/test_occ_commands_k8s.py + + - name: Collect HaRP logs + if: always() + run: docker logs appapi-harp > harp.log 2>&1 + + - name: Collect K8s resources + if: always() + run: | + kubectl -n nextcloud-exapps get all -o wide > k8s-resources.txt 2>&1 || true + kubectl -n nextcloud-exapps describe pods > k8s-pods-describe.txt 2>&1 || true + kubectl -n nextcloud-exapps get pvc -o wide >> k8s-resources.txt 2>&1 || true + + - name: Show all logs + if: always() + run: | + echo "=== HaRP logs ===" && cat harp.log || true + echo "=== K8s resources ===" && cat k8s-resources.txt || true + echo "=== K8s pods ===" && cat k8s-pods-describe.txt || true + echo "=== Nextcloud log (last 100 lines) ===" && tail -100 data/nextcloud.log || true + + - name: Upload HaRP logs + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_clusterip_harp.log + path: harp.log + if-no-files-found: warn + + - name: Upload K8s resources + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_clusterip_resources.txt + path: | + k8s-resources.txt + k8s-pods-describe.txt + if-no-files-found: warn + + - name: Upload NC logs + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_clusterip_nextcloud.log + path: data/nextcloud.log + if-no-files-found: warn + + tests-success: + permissions: + contents: none + runs-on: ubuntu-22.04 + needs: [k8s-deploy-clusterip] + name: K8s-ClusterIP-Tests-OK + steps: + - run: echo "K8s ClusterIP tests passed successfully" diff --git a/.github/workflows/tests-deploy-k8s-loadbalancer.yml b/.github/workflows/tests-deploy-k8s-loadbalancer.yml new file mode 100644 index 0000000..e8f66b2 --- /dev/null +++ b/.github/workflows/tests-deploy-k8s-loadbalancer.yml @@ -0,0 +1,241 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# Adapted from nextcloud/app_api's tests-deploy-k8s.yml so HaRP PRs exercise +# the integration with AppAPI against a freshly built HaRP image. +name: Tests - K8s Deploy (LoadBalancer) + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: tests-deploy-k8s-loadbalancer-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + HP_SHARED_KEY: 'test_shared_key_12345' + +jobs: + k8s-deploy-loadbalancer: + runs-on: ubuntu-22.04 + name: K8s Deploy Lifecycle (LoadBalancer) + + services: + postgres: + image: ghcr.io/nextcloud/continuous-integration-postgres-14:latest # zizmor: ignore[unpinned-images] + ports: + - 4444:5432/tcp + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: rootpassword + POSTGRES_DB: nextcloud + options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 + + steps: + - name: Checkout server + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + submodules: true + repository: nextcloud/server + ref: master + + - name: Checkout AppAPI + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + repository: nextcloud/app_api + ref: main + path: apps/app_api + + - name: Checkout HaRP (this PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + path: harp + + - name: Set up php + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 + with: + php-version: '8.3' + extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql + coverage: none + ini-file: development + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check composer file existence + id: check_composer + uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v2 + with: + files: apps/app_api/composer.json + + - name: Set up dependencies + if: steps.check_composer.outputs.files_exists == 'true' + working-directory: apps/app_api + run: composer i + + - name: Set up Nextcloud + env: + DB_PORT: 4444 + run: | + mkdir data + ./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 \ + --database-port=$DB_PORT --database-user=root --database-pass=rootpassword \ + --admin-user admin --admin-pass admin + ./occ config:system:set loglevel --value=0 --type=integer + ./occ config:system:set debug --value=true --type=boolean + ./occ app:enable --force app_api + + - name: Install k3s + run: | + curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik" sh - + sudo chmod 644 /etc/rancher/k3s/k3s.yaml + echo "KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> $GITHUB_ENV + + - name: Wait for k3s and create namespace + run: | + kubectl wait --for=condition=Ready node --all --timeout=120s + kubectl create namespace nextcloud-exapps + NODE_IP=$(kubectl get node -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') + echo "NODE_IP=${NODE_IP}" >> $GITHUB_ENV + echo "k3s node IP: $NODE_IP" + + - name: Configure Nextcloud for k3s networking + run: | + ./occ config:system:set overwrite.cli.url --value "http://${{ env.NODE_IP }}" --type=string + ./occ config:system:set trusted_domains 1 --value "${{ env.NODE_IP }}" + + - name: Wait for Klipper ServiceLB to be ready + run: | + echo "Waiting for svclb pods in kube-system..." + kubectl wait --for=condition=Ready pods -l app=svclb-traefik -n kube-system --timeout=60s 2>/dev/null || true + kubectl get pods -n kube-system | grep -E 'svclb|svc' || echo "No svclb pods yet (will start when first LB Service is created)" + + - name: Create K8s service account for HaRP + run: | + kubectl -n nextcloud-exapps create serviceaccount harp-sa + kubectl create clusterrolebinding harp-admin \ + --clusterrole=cluster-admin \ + --serviceaccount=nextcloud-exapps:harp-sa + K3S_TOKEN=$(kubectl -n nextcloud-exapps create token harp-sa --duration=2h) + echo "K3S_TOKEN=${K3S_TOKEN}" >> $GITHUB_ENV + + - name: Pre-pull ExApp image into k3s + run: sudo k3s ctr images pull ghcr.io/nextcloud/app-skeleton-python:latest + + - name: Build HaRP image (this PR) + working-directory: harp + run: docker build -t ghcr.io/nextcloud/nextcloud-appapi-harp:latest . + + - name: Start HaRP with K8s backend + run: | + docker run --net host --name appapi-harp \ + -e HP_SHARED_KEY="${{ env.HP_SHARED_KEY }}" \ + -e NC_INSTANCE_URL="http://${{ env.NODE_IP }}" \ + -e HP_LOG_LEVEL="debug" \ + -e HP_K8S_ENABLED="true" \ + -e HP_K8S_API_SERVER="https://127.0.0.1:6443" \ + -e HP_K8S_BEARER_TOKEN="${{ env.K3S_TOKEN }}" \ + -e HP_K8S_NAMESPACE="nextcloud-exapps" \ + -e HP_K8S_VERIFY_SSL="false" \ + --restart unless-stopped \ + -d ghcr.io/nextcloud/nextcloud-appapi-harp:latest + + - name: Start nginx proxy + run: | + docker run --net host --name nextcloud --rm \ + -v $(pwd)/apps/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf:/etc/nginx/conf.d/default.conf:ro \ + -d nginx + + - name: Start Nextcloud + run: PHP_CLI_SERVER_WORKERS=2 php -S 0.0.0.0:8080 & + + - name: Wait for HaRP K8s readiness + run: | + for i in $(seq 1 30); do + if curl -sf http://${{ env.NODE_IP }}:8780/exapps/app_api/info \ + -H "harp-shared-key: ${{ env.HP_SHARED_KEY }}" 2>/dev/null | grep -q '"kubernetes"'; then + echo "HaRP is ready with K8s backend" + exit 0 + fi + echo "Waiting for HaRP... ($i/30)" + sleep 2 + done + echo "HaRP K8s readiness check failed" + docker logs appapi-harp + exit 1 + + - name: Register K8s daemon (LoadBalancer) + run: | + ./occ app_api:daemon:register \ + k8s_test "K8s Test" "kubernetes-install" "http" "${{ env.NODE_IP }}:8780" "http://${{ env.NODE_IP }}" \ + --harp --harp_shared_key "${{ env.HP_SHARED_KEY }}" \ + --k8s --k8s_expose_type=loadbalancer --set-default + ./occ app_api:daemon:list + + - name: Run K8s integration tests (LoadBalancer) + env: + K8S_EXPOSE_TYPE: loadbalancer + run: python3 apps/app_api/tests/test_occ_commands_k8s.py + + - name: Collect HaRP logs + if: always() + run: docker logs appapi-harp > harp.log 2>&1 + + - name: Collect K8s resources + if: always() + run: | + kubectl -n nextcloud-exapps get all -o wide > k8s-resources.txt 2>&1 || true + kubectl -n nextcloud-exapps describe pods > k8s-pods-describe.txt 2>&1 || true + kubectl -n nextcloud-exapps get pvc -o wide >> k8s-resources.txt 2>&1 || true + kubectl -n nextcloud-exapps get svc -o yaml >> k8s-resources.txt 2>&1 || true + + - name: Show all logs + if: always() + run: | + echo "=== HaRP logs ===" && cat harp.log || true + echo "=== K8s resources ===" && cat k8s-resources.txt || true + echo "=== K8s pods ===" && cat k8s-pods-describe.txt || true + echo "=== Nextcloud log (last 100 lines) ===" && tail -100 data/nextcloud.log || true + + - name: Upload HaRP logs + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_loadbalancer_harp.log + path: harp.log + if-no-files-found: warn + + - name: Upload K8s resources + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_loadbalancer_resources.txt + path: | + k8s-resources.txt + k8s-pods-describe.txt + if-no-files-found: warn + + - name: Upload NC logs + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_loadbalancer_nextcloud.log + path: data/nextcloud.log + if-no-files-found: warn + + tests-success: + permissions: + contents: none + runs-on: ubuntu-22.04 + needs: [k8s-deploy-loadbalancer] + name: K8s-LoadBalancer-Tests-OK + steps: + - run: echo "K8s LoadBalancer tests passed successfully" diff --git a/.github/workflows/tests-deploy-k8s-manual.yml b/.github/workflows/tests-deploy-k8s-manual.yml new file mode 100644 index 0000000..86bbd0b --- /dev/null +++ b/.github/workflows/tests-deploy-k8s-manual.yml @@ -0,0 +1,236 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# Adapted from nextcloud/app_api's tests-deploy-k8s.yml so HaRP PRs exercise +# the integration with AppAPI against a freshly built HaRP image. +name: Tests - K8s Deploy (Manual) + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: tests-deploy-k8s-manual-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + HP_SHARED_KEY: 'test_shared_key_12345' + MANUAL_CLUSTER_IP: '10.43.200.200' + +jobs: + k8s-deploy-manual: + runs-on: ubuntu-22.04 + name: K8s Deploy Lifecycle (Manual) + + services: + postgres: + image: ghcr.io/nextcloud/continuous-integration-postgres-14:latest # zizmor: ignore[unpinned-images] + ports: + - 4444:5432/tcp + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: rootpassword + POSTGRES_DB: nextcloud + options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 + + steps: + - name: Checkout server + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + submodules: true + repository: nextcloud/server + ref: master + + - name: Checkout AppAPI + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + repository: nextcloud/app_api + ref: main + path: apps/app_api + + - name: Checkout HaRP (this PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + path: harp + + - name: Set up php + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 + with: + php-version: '8.3' + extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql + coverage: none + ini-file: development + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check composer file existence + id: check_composer + uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v2 + with: + files: apps/app_api/composer.json + + - name: Set up dependencies + if: steps.check_composer.outputs.files_exists == 'true' + working-directory: apps/app_api + run: composer i + + - name: Set up Nextcloud + env: + DB_PORT: 4444 + run: | + mkdir data + ./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 \ + --database-port=$DB_PORT --database-user=root --database-pass=rootpassword \ + --admin-user admin --admin-pass admin + ./occ config:system:set loglevel --value=0 --type=integer + ./occ config:system:set debug --value=true --type=boolean + ./occ app:enable --force app_api + + - name: Install k3s + run: | + curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik --disable servicelb" sh - + sudo chmod 644 /etc/rancher/k3s/k3s.yaml + echo "KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> $GITHUB_ENV + + - name: Wait for k3s and create namespace + run: | + kubectl wait --for=condition=Ready node --all --timeout=120s + kubectl create namespace nextcloud-exapps + NODE_IP=$(kubectl get node -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') + echo "NODE_IP=${NODE_IP}" >> $GITHUB_ENV + echo "k3s node IP: $NODE_IP" + + - name: Configure Nextcloud for k3s networking + run: | + ./occ config:system:set overwrite.cli.url --value "http://${{ env.NODE_IP }}" --type=string + ./occ config:system:set trusted_domains 1 --value "${{ env.NODE_IP }}" + + - name: Create K8s service account for HaRP + run: | + kubectl -n nextcloud-exapps create serviceaccount harp-sa + kubectl create clusterrolebinding harp-admin \ + --clusterrole=cluster-admin \ + --serviceaccount=nextcloud-exapps:harp-sa + K3S_TOKEN=$(kubectl -n nextcloud-exapps create token harp-sa --duration=2h) + echo "K3S_TOKEN=${K3S_TOKEN}" >> $GITHUB_ENV + + - name: Pre-pull ExApp image into k3s + run: sudo k3s ctr images pull ghcr.io/nextcloud/app-skeleton-python:latest + + - name: Build HaRP image (this PR) + working-directory: harp + run: docker build -t ghcr.io/nextcloud/nextcloud-appapi-harp:latest . + + - name: Start HaRP with K8s backend + run: | + docker run --net host --name appapi-harp \ + -e HP_SHARED_KEY="${{ env.HP_SHARED_KEY }}" \ + -e NC_INSTANCE_URL="http://${{ env.NODE_IP }}" \ + -e HP_LOG_LEVEL="debug" \ + -e HP_K8S_ENABLED="true" \ + -e HP_K8S_API_SERVER="https://127.0.0.1:6443" \ + -e HP_K8S_BEARER_TOKEN="${{ env.K3S_TOKEN }}" \ + -e HP_K8S_NAMESPACE="nextcloud-exapps" \ + -e HP_K8S_VERIFY_SSL="false" \ + --restart unless-stopped \ + -d ghcr.io/nextcloud/nextcloud-appapi-harp:latest + + - name: Start nginx proxy + run: | + docker run --net host --name nextcloud --rm \ + -v $(pwd)/apps/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf:/etc/nginx/conf.d/default.conf:ro \ + -d nginx + + - name: Start Nextcloud + run: PHP_CLI_SERVER_WORKERS=2 php -S 0.0.0.0:8080 & + + - name: Wait for HaRP K8s readiness + run: | + for i in $(seq 1 30); do + if curl -sf http://${{ env.NODE_IP }}:8780/exapps/app_api/info \ + -H "harp-shared-key: ${{ env.HP_SHARED_KEY }}" 2>/dev/null | grep -q '"kubernetes"'; then + echo "HaRP is ready with K8s backend" + exit 0 + fi + echo "Waiting for HaRP... ($i/30)" + sleep 2 + done + echo "HaRP K8s readiness check failed" + docker logs appapi-harp + exit 1 + + - name: Register K8s daemon (manual expose) + run: | + ./occ app_api:daemon:register \ + k8s_test "K8s Test" "kubernetes-install" "http" "${{ env.NODE_IP }}:8780" "http://${{ env.NODE_IP }}" \ + --harp --harp_shared_key "${{ env.HP_SHARED_KEY }}" \ + --k8s --k8s_expose_type=manual --k8s_upstream_host=${{ env.MANUAL_CLUSTER_IP }} --set-default + ./occ app_api:daemon:list + + - name: Run K8s integration tests (manual) + env: + K8S_EXPOSE_TYPE: manual + MANUAL_CLUSTER_IP: ${{ env.MANUAL_CLUSTER_IP }} + run: python3 apps/app_api/tests/test_occ_commands_k8s.py + + - name: Collect HaRP logs + if: always() + run: docker logs appapi-harp > harp.log 2>&1 + + - name: Collect K8s resources + if: always() + run: | + kubectl -n nextcloud-exapps get all -o wide > k8s-resources.txt 2>&1 || true + kubectl -n nextcloud-exapps describe pods > k8s-pods-describe.txt 2>&1 || true + kubectl -n nextcloud-exapps get pvc -o wide >> k8s-resources.txt 2>&1 || true + + - name: Show all logs + if: always() + run: | + echo "=== HaRP logs ===" && cat harp.log || true + echo "=== K8s resources ===" && cat k8s-resources.txt || true + echo "=== K8s pods ===" && cat k8s-pods-describe.txt || true + echo "=== Nextcloud log (last 100 lines) ===" && tail -100 data/nextcloud.log || true + + - name: Upload HaRP logs + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_manual_harp.log + path: harp.log + if-no-files-found: warn + + - name: Upload K8s resources + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_manual_resources.txt + path: | + k8s-resources.txt + k8s-pods-describe.txt + if-no-files-found: warn + + - name: Upload NC logs + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_manual_nextcloud.log + path: data/nextcloud.log + if-no-files-found: warn + + tests-success: + permissions: + contents: none + runs-on: ubuntu-22.04 + needs: [k8s-deploy-manual] + name: K8s-Manual-Tests-OK + steps: + - run: echo "K8s Manual expose tests passed successfully" diff --git a/.github/workflows/tests-deploy-k8s.yml b/.github/workflows/tests-deploy-k8s.yml new file mode 100644 index 0000000..f468d65 --- /dev/null +++ b/.github/workflows/tests-deploy-k8s.yml @@ -0,0 +1,234 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# Adapted from nextcloud/app_api's tests-deploy-k8s.yml so HaRP PRs exercise +# the integration with AppAPI against a freshly built HaRP image. +name: Tests - K8s Deploy (NodePort) + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: tests-deploy-k8s-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + HP_SHARED_KEY: 'test_shared_key_12345' + +jobs: + k8s-deploy-nodeport: + runs-on: ubuntu-22.04 + name: K8s Deploy Lifecycle (NodePort) + + services: + postgres: + image: ghcr.io/nextcloud/continuous-integration-postgres-14:latest # zizmor: ignore[unpinned-images] + ports: + - 4444:5432/tcp + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: rootpassword + POSTGRES_DB: nextcloud + options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 + + steps: + - name: Checkout server + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + submodules: true + repository: nextcloud/server + ref: master + + - name: Checkout AppAPI + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + repository: nextcloud/app_api + ref: main + path: apps/app_api + + - name: Checkout HaRP (this PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + path: harp + + - name: Set up php + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 + with: + php-version: '8.3' + extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql + coverage: none + ini-file: development + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check composer file existence + id: check_composer + uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v2 + with: + files: apps/app_api/composer.json + + - name: Set up dependencies + if: steps.check_composer.outputs.files_exists == 'true' + working-directory: apps/app_api + run: composer i + + - name: Set up Nextcloud + env: + DB_PORT: 4444 + run: | + mkdir data + ./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 \ + --database-port=$DB_PORT --database-user=root --database-pass=rootpassword \ + --admin-user admin --admin-pass admin + ./occ config:system:set loglevel --value=0 --type=integer + ./occ config:system:set debug --value=true --type=boolean + ./occ app:enable --force app_api + + - name: Install k3s + run: | + curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik --disable servicelb" sh - + sudo chmod 644 /etc/rancher/k3s/k3s.yaml + echo "KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> $GITHUB_ENV + + - name: Wait for k3s and create namespace + run: | + kubectl wait --for=condition=Ready node --all --timeout=120s + kubectl create namespace nextcloud-exapps + NODE_IP=$(kubectl get node -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') + echo "NODE_IP=${NODE_IP}" >> $GITHUB_ENV + echo "k3s node IP: $NODE_IP" + + - name: Configure Nextcloud for k3s networking + run: | + ./occ config:system:set overwrite.cli.url --value "http://${{ env.NODE_IP }}" --type=string + ./occ config:system:set trusted_domains 1 --value "${{ env.NODE_IP }}" + + - name: Create K8s service account for HaRP + run: | + kubectl -n nextcloud-exapps create serviceaccount harp-sa + kubectl create clusterrolebinding harp-admin \ + --clusterrole=cluster-admin \ + --serviceaccount=nextcloud-exapps:harp-sa + K3S_TOKEN=$(kubectl -n nextcloud-exapps create token harp-sa --duration=2h) + echo "K3S_TOKEN=${K3S_TOKEN}" >> $GITHUB_ENV + + - name: Pre-pull ExApp image into k3s + run: sudo k3s ctr images pull ghcr.io/nextcloud/app-skeleton-python:latest + + - name: Build HaRP image (this PR) + working-directory: harp + run: docker build -t ghcr.io/nextcloud/nextcloud-appapi-harp:latest . + + - name: Start HaRP with K8s backend + run: | + docker run --net host --name appapi-harp \ + -e HP_SHARED_KEY="${{ env.HP_SHARED_KEY }}" \ + -e NC_INSTANCE_URL="http://${{ env.NODE_IP }}" \ + -e HP_LOG_LEVEL="debug" \ + -e HP_K8S_ENABLED="true" \ + -e HP_K8S_API_SERVER="https://127.0.0.1:6443" \ + -e HP_K8S_BEARER_TOKEN="${{ env.K3S_TOKEN }}" \ + -e HP_K8S_NAMESPACE="nextcloud-exapps" \ + -e HP_K8S_VERIFY_SSL="false" \ + --restart unless-stopped \ + -d ghcr.io/nextcloud/nextcloud-appapi-harp:latest + + - name: Start nginx proxy + run: | + docker run --net host --name nextcloud --rm \ + -v $(pwd)/apps/app_api/tests/simple-nginx-NOT-FOR-PRODUCTION.conf:/etc/nginx/conf.d/default.conf:ro \ + -d nginx + + - name: Start Nextcloud + run: PHP_CLI_SERVER_WORKERS=2 php -S 0.0.0.0:8080 & + + - name: Wait for HaRP K8s readiness + run: | + for i in $(seq 1 30); do + if curl -sf http://${{ env.NODE_IP }}:8780/exapps/app_api/info \ + -H "harp-shared-key: ${{ env.HP_SHARED_KEY }}" 2>/dev/null | grep -q '"kubernetes"'; then + echo "HaRP is ready with K8s backend" + exit 0 + fi + echo "Waiting for HaRP... ($i/30)" + sleep 2 + done + echo "HaRP K8s readiness check failed" + docker logs appapi-harp + exit 1 + + - name: Register K8s daemon + run: | + ./occ app_api:daemon:register \ + k8s_test "K8s Test" "kubernetes-install" "http" "${{ env.NODE_IP }}:8780" "http://${{ env.NODE_IP }}" \ + --harp --harp_shared_key "${{ env.HP_SHARED_KEY }}" \ + --k8s --k8s_expose_type=nodeport --set-default + ./occ app_api:daemon:list + + - name: Run K8s integration tests + env: + K8S_EXPOSE_TYPE: nodeport + run: python3 apps/app_api/tests/test_occ_commands_k8s.py + + - name: Collect HaRP logs + if: always() + run: docker logs appapi-harp > harp.log 2>&1 + + - name: Collect K8s resources + if: always() + run: | + kubectl -n nextcloud-exapps get all -o wide > k8s-resources.txt 2>&1 || true + kubectl -n nextcloud-exapps describe pods > k8s-pods-describe.txt 2>&1 || true + kubectl -n nextcloud-exapps get pvc -o wide >> k8s-resources.txt 2>&1 || true + + - name: Show all logs + if: always() + run: | + echo "=== HaRP logs ===" && cat harp.log || true + echo "=== K8s resources ===" && cat k8s-resources.txt || true + echo "=== K8s pods ===" && cat k8s-pods-describe.txt || true + echo "=== Nextcloud log (last 100 lines) ===" && tail -100 data/nextcloud.log || true + + - name: Upload HaRP logs + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_nodeport_harp.log + path: harp.log + if-no-files-found: warn + + - name: Upload K8s resources + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_nodeport_resources.txt + path: | + k8s-resources.txt + k8s-pods-describe.txt + if-no-files-found: warn + + - name: Upload NC logs + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: k8s_deploy_nodeport_nextcloud.log + path: data/nextcloud.log + if-no-files-found: warn + + tests-success: + permissions: + contents: none + runs-on: ubuntu-22.04 + needs: [k8s-deploy-nodeport] + name: K8s-NodePort-Tests-OK + steps: + - run: echo "K8s NodePort tests passed successfully" diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..d817195 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. From 8b6d62db758c89c0bed79ff7bf6db800784a3f6a Mon Sep 17 00:00:00 2001 From: Oleksander Piskun Date: Tue, 14 Apr 2026 14:31:21 +0000 Subject: [PATCH 3/3] fix(k8s): drop cluster-wide CoreDNS patching; rely on pod-level hostAliases Signed-off-by: Oleksander Piskun --- haproxy_agent.py | 95 ------------------------------------------------ 1 file changed, 95 deletions(-) diff --git a/haproxy_agent.py b/haproxy_agent.py index 94d9f25..11d44a7 100644 --- a/haproxy_agent.py +++ b/haproxy_agent.py @@ -2070,98 +2070,6 @@ def _k8s_parse_host_aliases() -> list[dict[str, Any]]: return [{"ip": ip, "hostnames": hosts} for ip, hosts in ip_to_hosts.items()] -async def _k8s_ensure_coredns_host_aliases() -> None: - """Publish HP_K8S_HOST_ALIASES into the 'coredns-custom' ConfigMap so they resolve cluster-wide. - - Uses k3s's /etc/coredns/custom/*.server import convention (also honoured by any CoreDNS - Deployment that mounts a 'coredns-custom' ConfigMap with optional=true). The main - 'coredns' ConfigMap managed by the distribution operator is never touched; the 'reload' - plugin present in the stock Corefile picks up the new file within seconds, so no - Deployment restart is needed either. The ConfigMap key is prefixed 'harp-' so other - operators writing unrelated keys into 'coredns-custom' are not disturbed. - """ - if not K8S_ENABLED or not K8S_HOST_ALIASES_RAW.strip(): - return - - host_aliases = _k8s_parse_host_aliases() - if not host_aliases: - return - - # Build one standalone Corefile server block per zone. Entries share a single block - # to keep the file compact and to avoid plugin-instance conflicts between zones. - hosts_entries: list[str] = [] - zone_names: list[str] = [] - for alias in host_aliases: - for hostname in alias["hostnames"]: - hosts_entries.append(f" {alias['ip']} {hostname}") - zone_names.append(f"{hostname}:53") - - server_file = ( - f"{' '.join(zone_names)} {{\n" - " hosts {\n" - f"{chr(10).join(hosts_entries)}\n" - " fallthrough\n" - " }\n" - " forward . /etc/resolv.conf\n" - "}\n" - ) - - cm_path = "/api/v1/namespaces/kube-system/configmaps/coredns-custom" - key = "harp-host-aliases.server" - - LOGGER.info("Publishing %d host alias zone(s) into coredns-custom: %s", len(zone_names), K8S_HOST_ALIASES_RAW) - - try: - status, existing, _text = await _k8s_request("GET", cm_path) - - if status == 404: - configmap = { - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "name": "coredns-custom", - "namespace": "kube-system", - "labels": {"app.kubernetes.io/managed-by": "harp"}, - }, - "data": {key: server_file}, - } - status, _, _text = await _k8s_request( - "POST", - "/api/v1/namespaces/kube-system/configmaps", - json_body=configmap, - ) - if status in (200, 201): - LOGGER.info("Created coredns-custom ConfigMap with HaRP host aliases.") - else: - LOGGER.warning("Failed to create coredns-custom (HTTP %d): %s", status, _text[:200]) - return - - if status != 200 or not isinstance(existing, dict): - LOGGER.warning("Could not read coredns-custom (HTTP %d), skipping.", status) - return - - current = (existing.get("data") or {}).get(key) - if current == server_file: - LOGGER.info("coredns-custom already carries the expected HaRP host aliases, no patch needed.") - return - - # Strategic merge patch on ConfigMap.data merges keys individually — unrelated keys - # written by other operators stay intact. - status, _, _text = await _k8s_request( - "PATCH", - cm_path, - json_body={"data": {key: server_file}}, - content_type="application/strategic-merge-patch+json", - ) - if status == 200: - LOGGER.info("Updated coredns-custom with HaRP host aliases.") - else: - LOGGER.warning("Failed to patch coredns-custom (HTTP %d): %s", status, _text[:200]) - - except Exception as exc: - LOGGER.warning("Failed to configure coredns-custom host aliases (non-fatal): %s", exc) - - def _k8s_build_deployment_manifest(payload: CreateExAppPayload, replicas: int) -> dict[str, Any]: """Build a Deployment manifest from CreateExAppPayload.""" deployment_name = payload.exapp_k8s_name @@ -3086,9 +2994,6 @@ async def run_http_server(host="127.0.0.1", port=8200): async def main(): - # Ensure cluster-wide DNS for host aliases before starting servers. - await _k8s_ensure_coredns_host_aliases() - spoa_task = asyncio.create_task(SPOA_AGENT._run(host=SPOA_HOST, port=SPOA_PORT)) # noqa http_task = asyncio.create_task(run_http_server(host="127.0.0.1", port=8200))