From 5b567abbbc3638836a05f17f99ba91c4e095b74c Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 2 Oct 2026 11:58:21 +0200 Subject: [PATCH 01/55] Decouple sdk version of update script --- examples/python_control_interface/Dockerfile | 2 +- examples/python_sdk_hello/Dockerfile | 2 +- examples/python_sdk_interactive/Dockerfile | 2 +- examples/python_sdk_logging/Dockerfile | 2 +- examples/rust_control_interface/Dockerfile | 2 +- examples/rust_sdk_hello/Dockerfile | 4 +-- examples/rust_sdk_logging/Dockerfile | 4 +-- tools/update_version.sh | 38 ++++++++++++++++---- 8 files changed, 40 insertions(+), 16 deletions(-) diff --git a/examples/python_control_interface/Dockerfile b/examples/python_control_interface/Dockerfile index a8ea4ccde..2335d27e1 100644 --- a/examples/python_control_interface/Dockerfile +++ b/examples/python_control_interface/Dockerfile @@ -20,6 +20,6 @@ FROM base ENV PYTHONPATH="${PYTHONPATH}:/usr/local/lib/ankaios" COPY --from=dev /usr/local/lib/ankaios /usr/local/lib/ankaios COPY examples/python_control_interface /ankaios -ENV ANKAIOS_VERSION=1.1.0-pre +ENV ANKAIOS_SDK_VERSION=1.0.1 ENTRYPOINT ["python3", "-u", "/ankaios/src/main.py"] diff --git a/examples/python_sdk_hello/Dockerfile b/examples/python_sdk_hello/Dockerfile index abdf9457e..aeea43241 100644 --- a/examples/python_sdk_hello/Dockerfile +++ b/examples/python_sdk_hello/Dockerfile @@ -13,6 +13,6 @@ COPY examples/tools/setup_python_sdk.sh . RUN if [ -z "$PYTHON_SDK_BRANCH" ] ; then ./setup_python_sdk.sh ; else ./setup_python_sdk.sh --sdk-source github --sdk-branch "$PYTHON_SDK_BRANCH" --proto-source local --proto-path . ; fi COPY examples/python_sdk_hello /ankaios -ENV ANKAIOS_VERSION=1.1.0-pre +ENV ANKAIOS_SDK_VERSION=1.0.1 ENTRYPOINT ["python3", "-u", "/ankaios/src/main.py"] diff --git a/examples/python_sdk_interactive/Dockerfile b/examples/python_sdk_interactive/Dockerfile index a5148eb57..cce749ef5 100644 --- a/examples/python_sdk_interactive/Dockerfile +++ b/examples/python_sdk_interactive/Dockerfile @@ -13,6 +13,6 @@ COPY examples/tools/setup_python_sdk.sh . RUN if [ -z "$PYTHON_SDK_BRANCH" ] ; then ./setup_python_sdk.sh ; else ./setup_python_sdk.sh --sdk-source github --sdk-branch "$PYTHON_SDK_BRANCH" --proto-source local --proto-path . ; fi COPY examples/python_sdk_interactive /ankaios -ENV ANKAIOS_VERSION=1.1.0-pre +ENV ANKAIOS_SDK_VERSION=1.0.1 ENTRYPOINT ["python3", "-u", "/ankaios/src/main.py"] diff --git a/examples/python_sdk_logging/Dockerfile b/examples/python_sdk_logging/Dockerfile index 0db742022..c8bc301d7 100644 --- a/examples/python_sdk_logging/Dockerfile +++ b/examples/python_sdk_logging/Dockerfile @@ -13,6 +13,6 @@ COPY examples/tools/setup_python_sdk.sh . RUN if [ -z "$PYTHON_SDK_BRANCH" ] ; then ./setup_python_sdk.sh ; else ./setup_python_sdk.sh --sdk-source github --sdk-branch "$PYTHON_SDK_BRANCH" --proto-source local --proto-path . ; fi COPY examples/python_sdk_logging /ankaios -ENV ANKAIOS_VERSION=1.1.0-pre +ENV ANKAIOS_SDK_VERSION=1.0.1 ENTRYPOINT ["python3", "-u", "/ankaios/src/main.py"] diff --git a/examples/rust_control_interface/Dockerfile b/examples/rust_control_interface/Dockerfile index c86ae153b..4894379fb 100644 --- a/examples/rust_control_interface/Dockerfile +++ b/examples/rust_control_interface/Dockerfile @@ -14,7 +14,7 @@ COPY ankaios_api ${WORKSPACE_DIR}/ankaios_api COPY spec_macros ${WORKSPACE_DIR}/spec_macros COPY examples/rust_control_interface ${WORKSPACE_DIR} WORKDIR ${WORKSPACE_DIR} -ENV ANKAIOS_VERSION=1.1.0-pre +ENV ANKAIOS_SDK_VERSION=1.0.1 RUN --mount=type=cache,target=${WORKSPACE_DIR}/target/release cargo build --release \ && cp ${WORKSPACE_DIR}/target/release/control_interface_example /usr/local/bin/ diff --git a/examples/rust_sdk_hello/Dockerfile b/examples/rust_sdk_hello/Dockerfile index e4ab8e3bd..72322e2be 100644 --- a/examples/rust_sdk_hello/Dockerfile +++ b/examples/rust_sdk_hello/Dockerfile @@ -12,12 +12,12 @@ RUN apt-get update && apt-get install -y \ && rm -rf /var/lib/apt/lists/* WORKDIR ${WORKSPACE_DIR}/ankaios_example -ENV ANKAIOS_VERSION=1.1.0-pre +ENV ANKAIOS_SDK_VERSION=1.0.1 COPY examples/rust_sdk_hello ${WORKSPACE_DIR}/ankaios_example COPY examples/tools/setup_rust_sdk.sh . -RUN if [ -z "$RUST_SDK_BRANCH" ] ; then ./setup_rust_sdk.sh --sdk-source crates --sdk-version "$ANKAIOS_VERSION" ; else ./setup_rust_sdk.sh --sdk-source github --sdk-branch "$RUST_SDK_BRANCH" ; fi +RUN if [ -z "$RUST_SDK_BRANCH" ] ; then ./setup_rust_sdk.sh --sdk-source crates --sdk-version "$ANKAIOS_SDK_VERSION" ; else ./setup_rust_sdk.sh --sdk-source github --sdk-branch "$RUST_SDK_BRANCH" ; fi RUN --mount=type=cache,target=${WORKSPACE_DIR}/target/release cargo build --release \ && cp ${WORKSPACE_DIR}/ankaios_example/target/release/rust_sdk_hello /usr/local/bin/rust_sdk_hello diff --git a/examples/rust_sdk_logging/Dockerfile b/examples/rust_sdk_logging/Dockerfile index b9b703a89..03870cbc3 100644 --- a/examples/rust_sdk_logging/Dockerfile +++ b/examples/rust_sdk_logging/Dockerfile @@ -12,12 +12,12 @@ RUN apt-get update && apt-get install -y \ && rm -rf /var/lib/apt/lists/* WORKDIR ${WORKSPACE_DIR}/ankaios_example -ENV ANKAIOS_VERSION=1.1.0-pre +ENV ANKAIOS_SDK_VERSION=1.0.1 COPY examples/rust_sdk_logging ${WORKSPACE_DIR}/ankaios_example COPY examples/tools/setup_rust_sdk.sh . -RUN if [ -z "$RUST_SDK_BRANCH" ] ; then ./setup_rust_sdk.sh --sdk-source crates --sdk-version "$ANKAIOS_VERSION" ; else ./setup_rust_sdk.sh --sdk-source github --sdk-branch "$RUST_SDK_BRANCH" ; fi +RUN if [ -z "$RUST_SDK_BRANCH" ] ; then ./setup_rust_sdk.sh --sdk-source crates --sdk-version "$ANKAIOS_SDK_VERSION" ; else ./setup_rust_sdk.sh --sdk-source github --sdk-branch "$RUST_SDK_BRANCH" ; fi RUN --mount=type=cache,target=${WORKSPACE_DIR}/target/release cargo build --release \ && cp ${WORKSPACE_DIR}/ankaios_example/target/release/rust_sdk_logging /usr/local/bin/rust_sdk_logging diff --git a/tools/update_version.sh b/tools/update_version.sh index c15a04675..55241bf1d 100755 --- a/tools/update_version.sh +++ b/tools/update_version.sh @@ -21,8 +21,8 @@ base_dir="$script_dir/.." workspace_config="$base_dir/Cargo.toml" usage() { - echo "Usage: $0 VERSION" - echo "Update Ankaios files to VERSION." + echo "Usage: $0 --version VERSION [--sdk-version SDK_VERSION]" + echo "Update Ankaios files to VERSION and optionally SDK files to SDK_VERSION." exit 1 } @@ -32,18 +32,39 @@ log_update() { # Initialize variables version="" +sdk_version="" # Parse arguments while [[ "$#" -gt 0 ]]; do case $1 in -h|--help) usage ;; - *) - if [[ -z "$version" ]]; then - version="$1" - else - echo "Error: Unknown parameter passed: $1" + --version|--sdk-version|--sdk_version) + if [[ "$#" -lt 2 || -z "$2" || "$2" == --* ]]; then + echo "Error: $1 requires a value." + usage + fi + case $1 in + --version) version="$2" ;; + --sdk-version|--sdk_version) sdk_version="$2" ;; + esac + shift 2 + ;; + --version=*|--sdk-version=*|--sdk_version=*) + option="${1%%=*}" + value="${1#*=}" + if [[ -z "$value" ]]; then + echo "Error: $option requires a value." usage fi + case $option in + --version) version="$value" ;; + --sdk-version|--sdk_version) sdk_version="$value" ;; + esac + shift + ;; + *) + echo "Error: Unknown parameter passed: $1" + usage shift ;; esac @@ -71,4 +92,7 @@ for example in $examples; do dockerfile="$base_dir/examples/$example/Dockerfile" log_update "$dockerfile" sed -i "s/^ENV ANKAIOS_VERSION=.*/ENV ANKAIOS_VERSION=${version}/" "$dockerfile" + if [[ -n "$sdk_version" ]]; then + sed -i "s/^ENV ANKAIOS_SDK_VERSION=.*/ENV ANKAIOS_SDK_VERSION=${sdk_version}/" "$dockerfile" + fi done From 2890a4caa8f6e9c8cf155c2b8a7b50109d68bc05 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 2 Oct 2026 12:05:23 +0200 Subject: [PATCH 02/55] Revert version env var in python sdk and control interface examples --- examples/python_control_interface/Dockerfile | 2 +- examples/python_sdk_hello/Dockerfile | 2 +- examples/python_sdk_interactive/Dockerfile | 2 +- examples/python_sdk_logging/Dockerfile | 2 +- examples/rust_control_interface/Dockerfile | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/python_control_interface/Dockerfile b/examples/python_control_interface/Dockerfile index 2335d27e1..a8ea4ccde 100644 --- a/examples/python_control_interface/Dockerfile +++ b/examples/python_control_interface/Dockerfile @@ -20,6 +20,6 @@ FROM base ENV PYTHONPATH="${PYTHONPATH}:/usr/local/lib/ankaios" COPY --from=dev /usr/local/lib/ankaios /usr/local/lib/ankaios COPY examples/python_control_interface /ankaios -ENV ANKAIOS_SDK_VERSION=1.0.1 +ENV ANKAIOS_VERSION=1.1.0-pre ENTRYPOINT ["python3", "-u", "/ankaios/src/main.py"] diff --git a/examples/python_sdk_hello/Dockerfile b/examples/python_sdk_hello/Dockerfile index aeea43241..abdf9457e 100644 --- a/examples/python_sdk_hello/Dockerfile +++ b/examples/python_sdk_hello/Dockerfile @@ -13,6 +13,6 @@ COPY examples/tools/setup_python_sdk.sh . RUN if [ -z "$PYTHON_SDK_BRANCH" ] ; then ./setup_python_sdk.sh ; else ./setup_python_sdk.sh --sdk-source github --sdk-branch "$PYTHON_SDK_BRANCH" --proto-source local --proto-path . ; fi COPY examples/python_sdk_hello /ankaios -ENV ANKAIOS_SDK_VERSION=1.0.1 +ENV ANKAIOS_VERSION=1.1.0-pre ENTRYPOINT ["python3", "-u", "/ankaios/src/main.py"] diff --git a/examples/python_sdk_interactive/Dockerfile b/examples/python_sdk_interactive/Dockerfile index cce749ef5..a5148eb57 100644 --- a/examples/python_sdk_interactive/Dockerfile +++ b/examples/python_sdk_interactive/Dockerfile @@ -13,6 +13,6 @@ COPY examples/tools/setup_python_sdk.sh . RUN if [ -z "$PYTHON_SDK_BRANCH" ] ; then ./setup_python_sdk.sh ; else ./setup_python_sdk.sh --sdk-source github --sdk-branch "$PYTHON_SDK_BRANCH" --proto-source local --proto-path . ; fi COPY examples/python_sdk_interactive /ankaios -ENV ANKAIOS_SDK_VERSION=1.0.1 +ENV ANKAIOS_VERSION=1.1.0-pre ENTRYPOINT ["python3", "-u", "/ankaios/src/main.py"] diff --git a/examples/python_sdk_logging/Dockerfile b/examples/python_sdk_logging/Dockerfile index c8bc301d7..0db742022 100644 --- a/examples/python_sdk_logging/Dockerfile +++ b/examples/python_sdk_logging/Dockerfile @@ -13,6 +13,6 @@ COPY examples/tools/setup_python_sdk.sh . RUN if [ -z "$PYTHON_SDK_BRANCH" ] ; then ./setup_python_sdk.sh ; else ./setup_python_sdk.sh --sdk-source github --sdk-branch "$PYTHON_SDK_BRANCH" --proto-source local --proto-path . ; fi COPY examples/python_sdk_logging /ankaios -ENV ANKAIOS_SDK_VERSION=1.0.1 +ENV ANKAIOS_VERSION=1.1.0-pre ENTRYPOINT ["python3", "-u", "/ankaios/src/main.py"] diff --git a/examples/rust_control_interface/Dockerfile b/examples/rust_control_interface/Dockerfile index 4894379fb..c86ae153b 100644 --- a/examples/rust_control_interface/Dockerfile +++ b/examples/rust_control_interface/Dockerfile @@ -14,7 +14,7 @@ COPY ankaios_api ${WORKSPACE_DIR}/ankaios_api COPY spec_macros ${WORKSPACE_DIR}/spec_macros COPY examples/rust_control_interface ${WORKSPACE_DIR} WORKDIR ${WORKSPACE_DIR} -ENV ANKAIOS_SDK_VERSION=1.0.1 +ENV ANKAIOS_VERSION=1.1.0-pre RUN --mount=type=cache,target=${WORKSPACE_DIR}/target/release cargo build --release \ && cp ${WORKSPACE_DIR}/target/release/control_interface_example /usr/local/bin/ From f8268f917a75cdb72e147f3797bdad77aeedf3bb Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 2 Oct 2026 12:32:00 +0200 Subject: [PATCH 03/55] Fix naming in update_version script --- tools/update_version.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/update_version.sh b/tools/update_version.sh index 55241bf1d..aa9273860 100755 --- a/tools/update_version.sh +++ b/tools/update_version.sh @@ -32,24 +32,24 @@ log_update() { # Initialize variables version="" -sdk_version="" +rust_sdk_version="" # Parse arguments while [[ "$#" -gt 0 ]]; do case $1 in -h|--help) usage ;; - --version|--sdk-version|--sdk_version) + --version|--sdk-version|--rust_sdk_version) if [[ "$#" -lt 2 || -z "$2" || "$2" == --* ]]; then echo "Error: $1 requires a value." usage fi case $1 in --version) version="$2" ;; - --sdk-version|--sdk_version) sdk_version="$2" ;; + --sdk-version|--rust_sdk_version) rust_sdk_version="$2" ;; esac shift 2 ;; - --version=*|--sdk-version=*|--sdk_version=*) + --version=*|--sdk-version=*|--rust_sdk_version=*) option="${1%%=*}" value="${1#*=}" if [[ -z "$value" ]]; then @@ -58,7 +58,7 @@ while [[ "$#" -gt 0 ]]; do fi case $option in --version) version="$value" ;; - --sdk-version|--sdk_version) sdk_version="$value" ;; + --sdk-version|--rust_sdk_version) rust_sdk_version="$value" ;; esac shift ;; @@ -92,7 +92,7 @@ for example in $examples; do dockerfile="$base_dir/examples/$example/Dockerfile" log_update "$dockerfile" sed -i "s/^ENV ANKAIOS_VERSION=.*/ENV ANKAIOS_VERSION=${version}/" "$dockerfile" - if [[ -n "$sdk_version" ]]; then - sed -i "s/^ENV ANKAIOS_SDK_VERSION=.*/ENV ANKAIOS_SDK_VERSION=${sdk_version}/" "$dockerfile" + if [[ -n "$rust_sdk_version" ]]; then + sed -i "s/^ENV ANKAIOS_SDK_VERSION=.*/ENV ANKAIOS_SDK_VERSION=${rust_sdk_version}/" "$dockerfile" fi done From 1aead11b0cc32a111e757b08ea019613f98141aa Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 2 Oct 2026 12:34:14 +0200 Subject: [PATCH 04/55] Fix update_version script --- tools/update_version.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/update_version.sh b/tools/update_version.sh index aa9273860..91ce54df9 100755 --- a/tools/update_version.sh +++ b/tools/update_version.sh @@ -38,18 +38,18 @@ rust_sdk_version="" while [[ "$#" -gt 0 ]]; do case $1 in -h|--help) usage ;; - --version|--sdk-version|--rust_sdk_version) + --version|--rust_sdk_version) if [[ "$#" -lt 2 || -z "$2" || "$2" == --* ]]; then echo "Error: $1 requires a value." usage fi case $1 in --version) version="$2" ;; - --sdk-version|--rust_sdk_version) rust_sdk_version="$2" ;; + --rust_sdk_version) rust_sdk_version="$2" ;; esac shift 2 ;; - --version=*|--sdk-version=*|--rust_sdk_version=*) + --version=*|--rust_sdk_version=*) option="${1%%=*}" value="${1#*=}" if [[ -z "$value" ]]; then @@ -58,7 +58,7 @@ while [[ "$#" -gt 0 ]]; do fi case $option in --version) version="$value" ;; - --sdk-version|--rust_sdk_version) rust_sdk_version="$value" ;; + --rust_sdk_version) rust_sdk_version="$value" ;; esac shift ;; From 029b4eabca44f99a6c4f3a397fbaf01a10938c28 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 2 Oct 2026 12:40:00 +0200 Subject: [PATCH 05/55] Revert update_version script --- tools/update_version.sh | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/tools/update_version.sh b/tools/update_version.sh index 91ce54df9..c15a04675 100755 --- a/tools/update_version.sh +++ b/tools/update_version.sh @@ -21,8 +21,8 @@ base_dir="$script_dir/.." workspace_config="$base_dir/Cargo.toml" usage() { - echo "Usage: $0 --version VERSION [--sdk-version SDK_VERSION]" - echo "Update Ankaios files to VERSION and optionally SDK files to SDK_VERSION." + echo "Usage: $0 VERSION" + echo "Update Ankaios files to VERSION." exit 1 } @@ -32,39 +32,18 @@ log_update() { # Initialize variables version="" -rust_sdk_version="" # Parse arguments while [[ "$#" -gt 0 ]]; do case $1 in -h|--help) usage ;; - --version|--rust_sdk_version) - if [[ "$#" -lt 2 || -z "$2" || "$2" == --* ]]; then - echo "Error: $1 requires a value." - usage - fi - case $1 in - --version) version="$2" ;; - --rust_sdk_version) rust_sdk_version="$2" ;; - esac - shift 2 - ;; - --version=*|--rust_sdk_version=*) - option="${1%%=*}" - value="${1#*=}" - if [[ -z "$value" ]]; then - echo "Error: $option requires a value." + *) + if [[ -z "$version" ]]; then + version="$1" + else + echo "Error: Unknown parameter passed: $1" usage fi - case $option in - --version) version="$value" ;; - --rust_sdk_version) rust_sdk_version="$value" ;; - esac - shift - ;; - *) - echo "Error: Unknown parameter passed: $1" - usage shift ;; esac @@ -92,7 +71,4 @@ for example in $examples; do dockerfile="$base_dir/examples/$example/Dockerfile" log_update "$dockerfile" sed -i "s/^ENV ANKAIOS_VERSION=.*/ENV ANKAIOS_VERSION=${version}/" "$dockerfile" - if [[ -n "$rust_sdk_version" ]]; then - sed -i "s/^ENV ANKAIOS_SDK_VERSION=.*/ENV ANKAIOS_SDK_VERSION=${rust_sdk_version}/" "$dockerfile" - fi done From 53efe753fc7950a873ec559b3190004406dd6be1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 2 Oct 2026 13:13:52 +0200 Subject: [PATCH 06/55] Add config file comparison --- tools/install.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tools/install.sh b/tools/install.sh index 60d6ff253..71f0235e2 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -236,9 +236,15 @@ install_config_file() { local target_file="${target_dir}/${config_file}" if [ -e "${target_file}.conf" ]; then - echo "Keeping the existing config file '${target_file}.conf'." - tar -xzOf "${CONFIGS_FILE_NAME}" "${config_file}.conf" | ${sudo_cmd} tee "${target_file}.confnew" >/dev/null - echo " Created '${target_file}.confnew' with the default config. It is unused, but you can compare it with your existing config file." + if cmp -s \ + <(tar -xzOf "${CONFIGS_FILE_NAME}" "${config_file}.conf" | tr -d '[:space:]') \ + <(${sudo_cmd} cat "${target_file}.conf" | tr -d '[:space:]'); then + echo "Existing config file '${target_file}.conf' matches the default; no .confnew file needed." + else + echo "Keeping the existing config file '${target_file}.conf'." + tar -xzOf "${CONFIGS_FILE_NAME}" "${config_file}.conf" | ${sudo_cmd} tee "${target_file}.confnew" >/dev/null + echo " Created '${target_file}.confnew' with the default config. It is unused, but you can compare it with your existing config file." + fi else tar -xzOf "${CONFIGS_FILE_NAME}" "${config_file}.conf" | ${sudo_cmd} tee "${target_file}.conf" >/dev/null echo "Created config file '${target_file}.conf'." From 5bc1e7f6051b85961668bfa453d241277bf4df6c Mon Sep 17 00:00:00 2001 From: Holger Dormann Date: Thu, 8 Oct 2026 09:00:26 +0000 Subject: [PATCH 07/55] Replace MkDocs Material with Zensical --- .devcontainer/Dockerfile | 2 +- .devcontainer/Dockerfile.base | 12 +- .github/workflows/documentation.yml | 3 - .markdownlint.yml | 2 +- .vscode/settings.json | 6 +- doc/README.md | 7 -- doc/mkdocs.yml | 176 ---------------------------- doc/zensical.toml | 176 ++++++++++++++++++++++++++++ tools/README.md | 2 +- tools/generate_docs.sh | 22 +++- 10 files changed, 199 insertions(+), 209 deletions(-) delete mode 100644 doc/mkdocs.yml create mode 100644 doc/zensical.toml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index a6cd63197..9c70eb5cc 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -12,7 +12,7 @@ # # SPDX-License-Identifier: Apache-2.0 -FROM ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 +FROM ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 ARG USERNAME=vscode diff --git a/.devcontainer/Dockerfile.base b/.devcontainer/Dockerfile.base index 535429387..8b6dc7e85 100644 --- a/.devcontainer/Dockerfile.base +++ b/.devcontainer/Dockerfile.base @@ -36,16 +36,10 @@ RUN export DEBIAN_FRONTEND=noninteractive \ # java for plantuml default-jre \ graphviz \ - # MkDocs Material and social card + # Zensical python3 \ python3-pip \ - libcairo2-dev \ - libfreetype6-dev \ libffi-dev \ - libjpeg-dev \ - libpng-dev \ - libz-dev \ - pngquant \ # Others protobuf-compiler \ protobuf-compiler-grpc \ @@ -142,8 +136,8 @@ COPY oft /usr/local/bin RUN curl -sL https://github.com/plantuml/plantuml/releases/download/v1.2024.3/plantuml.jar -o /usr/local/bin/plantuml.jar COPY plantuml /usr/local/bin -# MkDocs Material -RUN PIP_BREAK_SYSTEM_PACKAGES=1 pip install mkdocs-material pillow cairosvg mike mkdocs-htmlproofer-plugin mkdocs-llmstxt robotframework==7.4.2 markdown==3.10.2 +# Zensical (mike is installed from the Zensical compatible fork) +RUN PIP_BREAK_SYSTEM_PACKAGES=1 pip install zensical git+https://github.com/squidfunk/mike.git robotframework==7.4.2 # protoc-gen-doc RUN curl -sL https://github.com/pseudomuto/protoc-gen-doc/releases/download/v1.5.1/protoc-gen-doc_1.5.1_linux_${TARGETARCH}.tar.gz | tar xz --directory=/usr/local/bin protoc-gen-doc diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 267e19423..578b8d73e 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -15,9 +15,6 @@ concurrency: group: "documentation-${{ github.head_ref || github.ref }}" cancel-in-progress: true -env: - ENABLED_HTMLPROOFER: false - jobs: documentation_changes: name: Check if documentation has changed diff --git a/.markdownlint.yml b/.markdownlint.yml index 9be953591..e0fc3eaa9 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -3,7 +3,7 @@ default: true -# Material for MkDocs needs 4 spaces to work +# Zensical needs 4 spaces to work MD007: indent: 4 diff --git a/.vscode/settings.json b/.vscode/settings.json index 11d914da6..e3c4dc816 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -35,15 +35,11 @@ }, "rust-analyzer.check.command": "clippy", "yaml.schemas": { - "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml", "file:///workspaces/ankaios/target/x86_64-unknown-linux-musl/debug/ank_schema.json": "*.ank.y*ml" }, "yaml.customTags": [ "!ENV scalar", - "!ENV sequence", - "tag:yaml.org,2002:python/name:materialx.emoji.to_svg", - "tag:yaml.org,2002:python/name:materialx.emoji.twemoji", - "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format" + "!ENV sequence" ], "plantuml.jar": "/usr/local/bin/plantuml.jar", "protobuf.diagnostics.namingConventions": false, diff --git a/doc/README.md b/doc/README.md index 7021638cd..e98ff48b0 100644 --- a/doc/README.md +++ b/doc/README.md @@ -9,10 +9,3 @@ To be published on . ``` and visit . Make sure to forward the port in case you are using the dev container. - -To have a faster build, you may want to disable the [htmlproofer plugin](https://github.com/manuzhang/mkdocs-htmlproofer-plugin): - -```shell -export ENABLED_HTMLPROOFER=false -../tools/generate_docs.sh serve -``` diff --git a/doc/mkdocs.yml b/doc/mkdocs.yml deleted file mode 100644 index 8340ac4c0..000000000 --- a/doc/mkdocs.yml +++ /dev/null @@ -1,176 +0,0 @@ -site_name: Eclipse Ankaios -site_url: https://eclipse-ankaios.github.io/ankaios -repo_url: https://github.com/eclipse-ankaios/ankaios -repo_name: eclipse-ankaios/ankaios -edit_uri: edit/main/doc/docs/ -copyright: Copyright © 2023-2026 Elektrobit Automotive GmbH and others - -theme: - name: material - logo: assets/Ankaios__logo_for_dark_bgrd_clipped.png - favicon: assets/favicon128x128.png - custom_dir: overrides - palette: - # Palette toggle for automatic mode - - media: "(prefers-color-scheme)" - toggle: - icon: material/brightness-auto - name: Switch to light mode - primary: teal - - # Palette toggle for light mode - - media: "(prefers-color-scheme: light)" - scheme: default - toggle: - icon: material/brightness-7 - name: Switch to dark mode - primary: teal - - # Palette toggle for dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - toggle: - icon: material/brightness-4 - name: Switch to light mode - primary: teal - - features: - - navigation.instant - - navigation.sections - - navigation.tabs - - navigation.top - - content.code.copy - - content.code.annotate - - content.action.edit - - content.tabs.link - - navigation.footer - -markdown_extensions: - - attr_list - - md_in_html - - admonition - - footnotes - - pymdownx.details - - pymdownx.highlight: - anchor_linenums: true - # line_spans: __span - # pygments_lang_class: true - - pymdownx.inlinehilite - - pymdownx.snippets - - pymdownx.smartsymbols - - pymdownx.superfences - - pymdownx.tasklist - - pymdownx.tabbed: - alternate_style: true - - toc: - permalink: true - toc_depth: 3 - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format - - pymdownx.emoji: - emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_svg - -plugins: - - social - - search - - mike - - htmlproofer: - enabled: !ENV [ENABLED_HTMLPROOFER, True] - raise_error_after_finish: True - ignore_pages: - - reference/_ankaios.proto.md - raise_error_excludes: - 404: ['https://crates.io/crates/symphony'] - - llmstxt: - full_output: llms-full.txt - base_url: !ENV [DOCS_VERSION_URL, 'https://eclipse-ankaios.github.io/ankaios/latest'] - markdown_description: | - Eclipse Ankaios is a lightweight workload orchestrator for embedded and automotive edge devices. - It manages containerized workloads across one server and multiple agents running on resource-constrained hardware. - Use this documentation to understand the architecture, configure manifests, interact with the Control Interface, and contribute to the project. - sections: - Overview: - - index.md - - architecture.md - Usage: - - usage/installation.md - - usage/quickstart.md - - "usage/tutorial-*.md" - - usage/awesome-ankaios.md - - usage/mtls-setup.md - - usage/shell-completion.md - - "usage/manifest/*.md" - Reference: - - "reference/*.md" - - "reference/**/*.md" - Upgrading: - - "usage/upgrading/*.md" - Contributing: - - "development/*.md" - - "development/**/*.md" - Support: - - support.md - -extra: - version: - provider: mike - -extra_css: - - stylesheets/extra.css - -nav: - - index.md - - architecture.md - - Usage: - - usage/installation.md - - usage/quickstart.md - - usage/tutorial-vehicle-signals.md - - usage/tutorial-fleet-management.md - - usage/tutorial-events.md - - usage/awesome-ankaios.md - - usage/mtls-setup.md - - usage/shell-completion.md - - Manifest: - - usage/manifest/config-objects.md - - usage/manifest/workload-files.md - - Upgrading: - - usage/upgrading/v0_7_to_v1_0.md - - usage/upgrading/v0_6_to_v0_7.md - - usage/upgrading/v0_5_to_v0_6.md - - usage/upgrading/v0_4_to_v0_5.md - - usage/upgrading/v0_3_to_v0_4.md - - usage/upgrading/v0_2_to_v0_3.md - - Reference: - - reference/startup-configuration.md - - reference/interacting-with-ankaios.md - - Control Interface: - - reference/control-interface.md - - reference/complete-state.md - - reference/events.md - - reference/inter-workload-dependencies.md - - reference/restart-policy.md - - reference/resource-usage.md - - reference/orch-comparison.md - - reference/glossary.md - - reference/config-files.md - - Protobuf data structures: reference/_ankaios.proto.md - - Contributing: - - development/build.md - - development/rust-coding-guidelines.md - - development/changing-the-api.md - - development/unit-verification.md - - development/run-unit-tests.md - - development/test-coverage.md - - development/system-tests.md - - development/requirement-tracing.md - - development/requirement-template.md - - development/extending-dev-container.md - - development/ci-cd.md - - development/ci-cd-release.md - - development/self-service.md - - development/documentation-guidelines.md - - support.md diff --git a/doc/zensical.toml b/doc/zensical.toml new file mode 100644 index 000000000..d88f8ffe8 --- /dev/null +++ b/doc/zensical.toml @@ -0,0 +1,176 @@ +[project] +site_name = "Eclipse Ankaios" +site_url = "https://eclipse-ankaios.github.io/ankaios" +repo_url = "https://github.com/eclipse-ankaios/ankaios" +repo_name = "eclipse-ankaios/ankaios" +edit_uri = "edit/main/doc/docs/" +copyright = "Copyright © 2023-2026 Elektrobit Automotive GmbH and others" +site_dir = "html" + +extra_css = ["stylesheets/extra.css"] + +nav = [ + "index.md", + "architecture.md", + { "Usage" = [ + "usage/installation.md", + "usage/quickstart.md", + "usage/tutorial-vehicle-signals.md", + "usage/tutorial-fleet-management.md", + "usage/tutorial-events.md", + "usage/awesome-ankaios.md", + "usage/mtls-setup.md", + "usage/shell-completion.md", + { "Manifest" = [ + "usage/manifest/config-objects.md", + "usage/manifest/workload-files.md", + ] }, + { "Upgrading" = [ + "usage/upgrading/v0_7_to_v1_0.md", + "usage/upgrading/v0_6_to_v0_7.md", + "usage/upgrading/v0_5_to_v0_6.md", + "usage/upgrading/v0_4_to_v0_5.md", + "usage/upgrading/v0_3_to_v0_4.md", + "usage/upgrading/v0_2_to_v0_3.md", + ] }, + ] }, + { "Reference" = [ + "reference/startup-configuration.md", + "reference/interacting-with-ankaios.md", + { "Control Interface" = [ + "reference/control-interface.md", + "reference/complete-state.md", + "reference/events.md", + ] }, + "reference/inter-workload-dependencies.md", + "reference/restart-policy.md", + "reference/resource-usage.md", + "reference/orch-comparison.md", + "reference/glossary.md", + "reference/config-files.md", + { "Protobuf data structures" = "reference/_ankaios.proto.md" }, + ] }, + { "Contributing" = [ + "development/build.md", + "development/rust-coding-guidelines.md", + "development/changing-the-api.md", + "development/unit-verification.md", + "development/run-unit-tests.md", + "development/test-coverage.md", + "development/system-tests.md", + "development/requirement-tracing.md", + "development/requirement-template.md", + "development/extending-dev-container.md", + "development/ci-cd.md", + "development/ci-cd-release.md", + "development/self-service.md", + "development/documentation-guidelines.md", + ] }, + "support.md", +] + +[project.theme] +# Preserves the look and feel of the former Material for MkDocs setup +variant = "classic" +logo = "assets/Ankaios__logo_for_dark_bgrd_clipped.png" +favicon = "assets/favicon128x128.png" +custom_dir = "overrides" +features = [ + "navigation.instant", + "navigation.sections", + "navigation.tabs", + "navigation.top", + "content.code.copy", + "content.code.annotate", + "content.action.edit", + "content.tabs.link", + "navigation.footer", +] + +[[project.theme.palette]] +media = "(prefers-color-scheme)" +primary = "teal" +toggle.icon = "material/brightness-auto" +toggle.name = "Switch to light mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +primary = "teal" +toggle.icon = "material/brightness-7" +toggle.name = "Switch to dark mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +primary = "teal" +toggle.icon = "material/brightness-4" +toggle.name = "Switch to light mode" + +[project.extra.version] +provider = "mike" + +[project.markdown_extensions] +attr_list = {} +md_in_html = {} +admonition = {} +footnotes = {} +toc.permalink = true +toc.toc_depth = 3 +pymdownx.details = {} +pymdownx.highlight.anchor_linenums = true +pymdownx.inlinehilite = {} +pymdownx.snippets = {} +pymdownx.smartsymbols = {} +pymdownx.tasklist = {} +pymdownx.tabbed.alternate_style = true +pymdownx.superfences.custom_fences = [ + { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" }, +] +pymdownx.emoji.emoji_index = "zensical.extensions.emoji.twemoji" +pymdownx.emoji.emoji_generator = "zensical.extensions.emoji.to_svg" + +[project.plugins.search] + +[project.plugins.social] + +[project.plugins.mike] + +[project.plugins.llmstxt] +full_output = "llms-full.txt" +# Rewritten by tools/generate_docs.sh when DOCS_VERSION_URL is set +base_url = "https://eclipse-ankaios.github.io/ankaios/latest" +markdown_description = """ +Eclipse Ankaios is a lightweight workload orchestrator for embedded and automotive edge devices. +It manages containerized workloads across one server and multiple agents running on resource-constrained hardware. +Use this documentation to understand the architecture, configure manifests, interact with the Control Interface, and contribute to the project. +""" + +[project.plugins.llmstxt.sections] +Overview = [ + "index.md", + "architecture.md", +] +Usage = [ + "usage/installation.md", + "usage/quickstart.md", + "usage/tutorial-*.md", + "usage/awesome-ankaios.md", + "usage/mtls-setup.md", + "usage/shell-completion.md", + "usage/manifest/*.md", +] +Reference = [ + "reference/*.md", + "reference/**/*.md", +] +Upgrading = [ + "usage/upgrading/*.md", +] +Contributing = [ + "development/*.md", + "development/**/*.md", +] +Support = [ + "support.md", +] diff --git a/tools/README.md b/tools/README.md index fa49a2f94..10e2e2774 100644 --- a/tools/README.md +++ b/tools/README.md @@ -28,7 +28,7 @@ Creates the artifacts and packages the necessary files required for a release of ## generate_docs.sh -Generates the documentation using MkDocs. Running the script with the `--help` will provide the complete commands list. +Generates the documentation using Zensical. Running the script with the `--help` will provide the complete commands list. ## generate_test_coverage_report.sh diff --git a/tools/generate_docs.sh b/tools/generate_docs.sh index b771a8585..9510b9eda 100755 --- a/tools/generate_docs.sh +++ b/tools/generate_docs.sh @@ -21,10 +21,10 @@ show_help() { cat << EOF Usage: $(basename "$0") [COMMAND] -Generate documentation from Protocol Buffer files and manage MkDocs documentation. +Generate documentation from Protocol Buffer files and manage Zensical documentation. Commands: - serve Start local MkDocs development server + serve Start local Zensical development server build Build static HTML documentation deploy Deploy documentation to main branch deploy-release Deploy specific version as latest @@ -54,13 +54,23 @@ cp "$base_dir/doc/" "$target_dir" -rul protoc --plugin=protoc-gen-doc="/usr/local/bin/protoc-gen-doc" --doc_out="$target_dir/docs/reference" --doc_opt=markdown,_ankaios.proto.md --proto_path="$base_dir/ankaios_api/proto" control_api.proto ank_base.proto echo "Generate Markdown from ./ankaios_api/proto done." +config_file="$target_dir/zensical.toml" + +# zensical.toml has no environment variable interpolation, so the version specific +# base URL of the llmstxt plugin is patched into the generated copy of the config. +set_llmstxt_base_url() { + sed -i "s|^base_url = .*|base_url = \"$1\"|" "$config_file" +} + if [[ "$1" = serve ]]; then - mkdocs serve --config-file "$target_dir/mkdocs.yml" + zensical serve --config-file "$config_file" elif [[ "$1" = build ]]; then - mkdocs build --config-file "$target_dir/mkdocs.yml" -d html + zensical build --config-file "$config_file" elif [[ "$1" = deploy ]]; then - DOCS_VERSION_URL="https://eclipse-ankaios.github.io/ankaios/main" mike deploy --push --config-file "$target_dir/mkdocs.yml" main + set_llmstxt_base_url "https://eclipse-ankaios.github.io/ankaios/main" + mike deploy --push --config-file "$config_file" main elif [[ "$1" = deploy-release && ! (-z "$2") ]]; then echo "Deploying documentation version $2" - DOCS_VERSION_URL="https://eclipse-ankaios.github.io/ankaios/$2" mike deploy --update-aliases --push --config-file "$target_dir/mkdocs.yml" "$2" latest + set_llmstxt_base_url "https://eclipse-ankaios.github.io/ankaios/$2" + mike deploy --update-aliases --push --config-file "$config_file" "$2" latest fi From ec55284e217a694eb92739e203c1c0c80338984a Mon Sep 17 00:00:00 2001 From: Holger Dormann Date: Thu, 8 Oct 2026 09:52:33 +0000 Subject: [PATCH 08/55] Update dev container version in workflows --- .github/workflows/build.yml | 16 ++++++++-------- .github/workflows/check-req-tracing.yml | 2 +- .github/workflows/documentation.yml | 2 +- .github/workflows/sonarcloud.yml | 2 +- .../control_interface_tester/Dockerfile | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6376969d7..3dcfbcadf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest if: ${{ startsWith(github.ref, 'refs/tags/') }} container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Vendor all dependencies locally @@ -44,7 +44,7 @@ jobs: RUSTC_WRAPPER: sccache CARGO_INCREMENTAL: "0" container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 options: --privileged steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -108,7 +108,7 @@ jobs: RUSTC_WRAPPER: sccache CARGO_INCREMENTAL: "0" container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 options: --privileged steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -159,7 +159,7 @@ jobs: RUSTC_WRAPPER: sccache CARGO_INCREMENTAL: "0" container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 options: --privileged steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -187,7 +187,7 @@ jobs: RUSTC_WRAPPER: sccache CARGO_INCREMENTAL: "0" container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 options: --privileged steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -219,7 +219,7 @@ jobs: RUSTC_WRAPPER: sccache CARGO_INCREMENTAL: "0" container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 options: --user root steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -245,7 +245,7 @@ jobs: needs: vendor runs-on: ubuntu-latest container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 options: --user root steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -264,7 +264,7 @@ jobs: needs: build_linux_amd64 runs-on: ubuntu-latest container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 options: --user root steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/check-req-tracing.yml b/.github/workflows/check-req-tracing.yml index caaa17410..af17b3926 100644 --- a/.github/workflows/check-req-tracing.yml +++ b/.github/workflows/check-req-tracing.yml @@ -8,7 +8,7 @@ jobs: permissions: write-all runs-on: ubuntu-latest container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Compare requirement tracing reports diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 578b8d73e..1dd22fc27 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -39,7 +39,7 @@ jobs: if: ${{ needs.documentation_changes.outputs.documentation == 'true' || github.ref_name == 'main' || github.ref_type == 'tag' }} runs-on: ubuntu-latest container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare commit to branch gh-pages diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index bf6217b28..c03b23e6d 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -37,7 +37,7 @@ jobs: RUSTC_WRAPPER: sccache CARGO_INCREMENTAL: "0" container: - image: ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 + image: ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 options: --privileged steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/tests/resources/control_interface_tester/Dockerfile b/tests/resources/control_interface_tester/Dockerfile index ad364b044..cb3a68968 100644 --- a/tests/resources/control_interface_tester/Dockerfile +++ b/tests/resources/control_interface_tester/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/eclipse-ankaios/devcontainer-base:1.3.0 AS compile +FROM ghcr.io/eclipse-ankaios/devcontainer-base:1.4.0 AS compile ARG WORKSPACE_DIR=/workspaces/build From f79c1aa8e4f6cd3a8dd3133a79ff2ce020ebd852 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 27 Aug 2026 08:33:13 +0200 Subject: [PATCH 09/55] Add support for unix domain sockets to ank-server --- Cargo.lock | 3 + grpc/Cargo.toml | 5 +- grpc/doc/swdesign/README.md | 17 +++ grpc/src/server.rs | 131 ++++++++++++++++- server/config/ank-server.conf | 9 +- server/doc/swdesign/README.md | 98 +++++++++++++ server/src/cli.rs | 13 +- server/src/main.rs | 54 ++++--- server/src/server_config.rs | 266 ++++++++++++++++++++++++++++++++-- 9 files changed, 544 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f516cd2ac..be13d8157 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1026,7 +1026,9 @@ dependencies = [ "common", "env_logger", "futures-core", + "hyper-util", "log", + "nix", "prost", "regex", "serde", @@ -1036,6 +1038,7 @@ dependencies = [ "tonic", "tonic-prost", "tonic-prost-build", + "tower", "uuid", "x509-parser", ] diff --git a/grpc/Cargo.toml b/grpc/Cargo.toml index 182fdd8fd..f4e0e6f21 100644 --- a/grpc/Cargo.toml +++ b/grpc/Cargo.toml @@ -23,12 +23,15 @@ tokio = { version = "1.41", features = [ "io-util", "process", ] } -tokio-stream = "0.1" +tokio-stream = { version = "0.1", features = ["net"] } serde = { version = "1.0", features = ["derive"] } log = "0.4" uuid = { version = "1.3", features = ["v4", "fast-rng"] } x509-parser = "0.18" regex = "1.12" +nix = { version = "0.31", features = ["fs", "user"] } +tower = "0.5" +hyper-util = "0.1" [dev-dependencies] ankaios-api = { path = "../ankaios_api", features = ["test_utils"] } diff --git a/grpc/doc/swdesign/README.md b/grpc/doc/swdesign/README.md index ac5683744..f116844a3 100644 --- a/grpc/doc/swdesign/README.md +++ b/grpc/doc/swdesign/README.md @@ -203,6 +203,23 @@ Needs: - impl - itest +#### gRPC Server supports unix domain socket endpoints +`swdd~grpc-server-supports-unix-domain-socket-endpoints~1` + +Status: approved + +The gRPC Server shall support listening for incoming gRPC connections on either a TCP socket endpoint or a Unix domain socket endpoint. + +Rationale: +Unix domain sockets allow local communication without exposing a TCP port. + +Tags: +- gRPC_Server + +Needs: +- impl +- itest + #### gRPC Server provides a gRPC endpoint for CLI connection handling `swdd~grpc-server-provides-endpoint-for-cli-connection-handling~1` diff --git a/grpc/src/server.rs b/grpc/src/server.rs index 8c3a14236..f509d2eca 100644 --- a/grpc/src/server.rs +++ b/grpc/src/server.rs @@ -24,12 +24,18 @@ use crate::grpc_middleware_error::GrpcMiddlewareError; use crate::security::TLSConfig; use common::communications_error::CommunicationMiddlewareError; -use common::communications_server::CommunicationsServer; +use common::communications_server::{CommunicationsServer, ServerConnection}; use common::from_server_interface::FromServerReceiver; use common::to_server_interface::ToServerSender; use async_trait::async_trait; -use std::net::SocketAddr; +use nix::unistd::{Group, chown}; +use std::fs; +use std::os::unix::fs::FileTypeExt; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use tokio::net::UnixListener; +use tokio_stream::wrappers::UnixListenerStream; use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; #[derive(Debug)] @@ -38,6 +44,7 @@ pub struct GRPCCommunicationsServer { agent_senders: ClientSendersMap, commander_senders: ClientSendersMap, tls_config: Option, + unix_socket_group: Option, } #[async_trait] @@ -45,8 +52,9 @@ impl CommunicationsServer for GRPCCommunicationsServer { async fn start( &mut self, mut receiver: FromServerReceiver, - addr: SocketAddr, + addr: ServerConnection, ) -> Result<(), CommunicationMiddlewareError> { + // [impl->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] // [impl->swdd~grpc-server-creates-agent-connection~1] let my_connection = GRPCAgentConnection::new(self.agent_senders.clone(), self.sender.clone()); @@ -62,9 +70,9 @@ impl CommunicationsServer for GRPCCommunicationsServer { let agent_senders_clone = self.agent_senders.clone(); let commander_senders_clone = self.commander_senders.clone(); - match &self.tls_config { + match (&self.tls_config, addr) { // [impl->swdd~grpc-server-activate-mtls-when-certificates-and-key-provided-upon-start~1] - Some(tls_config) => { + (Some(tls_config), ServerConnection::Tcp(tcp_addr)) => { let ca = &tls_config.ca_pem; let cert = &tls_config.crt_pem; let key = &tls_config.key_pem; @@ -84,7 +92,7 @@ impl CommunicationsServer for GRPCCommunicationsServer { // [impl->swdd~grpc-commander-uses-dedicated-server-endpoint~1] // [impl->swdd~grpc-server-provides-endpoint-for-commander-connection-handling~1] .add_service(CommandConnectionServer::new(my_commander_connection)) - .serve(addr) => { + .serve(tcp_addr) => { result.map_err(|err| { GrpcMiddlewareError::StartError(format!("{err:?}")) })? @@ -101,8 +109,12 @@ impl CommunicationsServer for GRPCCommunicationsServer { } } } + // [impl->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + (Some(_), ServerConnection::Unix(_)) => Err(CommunicationMiddlewareError( + "Invalid runtime config: TLS is not supported for unix:// endpoints".to_string(), + ))?, // [impl->swdd~grpc-server-deactivate-mtls-when-no-certificates-and-no-key-provided-upon-start~1] - None => { + (None, ServerConnection::Tcp(tcp_addr)) => { log::warn!( "!!!ANKSERVER IS STARTED IN INSECURE MODE (-k, --insecure) -> TLS is disabled!!!" ); @@ -115,7 +127,7 @@ impl CommunicationsServer for GRPCCommunicationsServer { .add_service(CliConnectionServer::new(my_cli_connection)) // [impl->swdd~grpc-server-provides-endpoint-for-commander-connection-handling~1] .add_service(CommandConnectionServer::new(my_commander_connection)) - .serve(addr) => { + .serve(tcp_addr) => { result.map_err(|err| { GrpcMiddlewareError::StartError(format!("{err:?}")) })? @@ -133,11 +145,107 @@ impl CommunicationsServer for GRPCCommunicationsServer { } } + // [impl->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + (None, ServerConnection::Unix(unix_socket_path)) => { + let listener = + prepare_unix_listener(&unix_socket_path, self.unix_socket_group.as_deref())?; + let incoming = UnixListenerStream::new(listener); + + tokio::select! { + result = Server::builder() + .add_service(AgentConnectionServer::new(my_connection)) + .add_service(CliConnectionServer::new(my_cli_connection)) + .add_service(CommandConnectionServer::new(my_commander_connection)) + .serve_with_incoming(incoming) => { + result.map_err(|err| { + GrpcMiddlewareError::StartError(format!("{err:?}")) + })? + } + _ = from_server_proxy::forward_from_ankaios_to_proto( + &agent_senders_clone, + &commander_senders_clone, + &mut receiver, + ) => { + Err(GrpcMiddlewareError::ConnectionInterrupted( + "Connection between Ankaios server and the communication middleware dropped.".into()) + )? + } + } + } } Ok(()) } } +fn prepare_unix_listener( + socket_path: &Path, + socket_group: Option<&str>, +) -> Result { + if socket_path.exists() { + let metadata = fs::metadata(socket_path).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not access existing unix socket path '{}': {err}", + socket_path.display() + )) + })?; + + if metadata.file_type().is_socket() { + fs::remove_file(socket_path).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not remove stale unix socket '{}': {err}", + socket_path.display() + )) + })?; + } else { + return Err(CommunicationMiddlewareError(format!( + "Unix socket path '{}' exists and is not a socket file", + socket_path.display() + ))); + } + } + + let listener = UnixListener::bind(socket_path).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not bind unix socket '{}': {err}", + socket_path.display() + )) + })?; + + if let Some(group_name) = socket_group { + // [impl->swdd~server-configures-unix-domain-socket-group~1] + let group = Group::from_name(group_name) + .map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not resolve unix socket group '{}': {err}", + group_name + )) + })? + .ok_or_else(|| { + CommunicationMiddlewareError(format!( + "Could not resolve unix socket group '{}': group does not exist", + group_name + )) + })?; + + chown(socket_path, None, Some(group.gid)).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not set unix socket group '{}' on '{}': {err}", + group_name, + socket_path.display() + )) + })?; + + fs::set_permissions(socket_path, fs::Permissions::from_mode(0o660)).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not set unix socket permissions on '{}': {err}", + socket_path.display() + )) + })?; + } + + Ok(listener) +} + impl GRPCCommunicationsServer { pub fn new(sender: ToServerSender, tls_config: Option) -> Self { GRPCCommunicationsServer { @@ -145,6 +253,13 @@ impl GRPCCommunicationsServer { commander_senders: ClientSendersMap::new(), sender, tls_config, + unix_socket_group: None, } } + + pub fn with_unix_socket_group(mut self, unix_socket_group: Option) -> Self { + // [impl->swdd~server-configures-unix-domain-socket-group~1] + self.unix_socket_group = unix_socket_group; + self + } } diff --git a/server/config/ank-server.conf b/server/config/ank-server.conf index 8ee3cdcb0..5e4b5279e 100644 --- a/server/config/ank-server.conf +++ b/server/config/ank-server.conf @@ -9,12 +9,17 @@ version = 'v1' startup_manifest = '/etc/ankaios/state.yaml' # The address, including the port, to which the server should listen. -# address = '127.0.0.1:25551' +address = 'unix:///run/ankaios/server.sock' + +# Optional socket group applied to Unix domain socket files. +# This option is only valid with unix:// addresses. +socket_group = 'ankaios' # The flag to disable TLS communication between # the Ankaios server, agents and the ank CLI. # If set to 'true' and the certificates are not provided, then the server shall not use TLS. -insecure = true +# This option must not be used with unix:// addresses. +# insecure = true # The path to ca certificate pem file. # ca_pem = '/etc/ankaios/certs/ca.pem' diff --git a/server/doc/swdesign/README.md b/server/doc/swdesign/README.md index 27a283e83..f0a433caa 100644 --- a/server/doc/swdesign/README.md +++ b/server/doc/swdesign/README.md @@ -73,6 +73,28 @@ Considered alternatives: - **Lightweight mutating result message**: The hook returns a small response carrying only workload names and an "admitted" flag when no mutation occurred. This would save a few KB of pipe I/O per hook but adds significant complexity: a new protobuf message type, a framing mechanism for the server to distinguish response formats, two code paths for response handling, and additional burden on hook authors. The pipe I/O savings (~10–50 microseconds total across 10 hooks) are negligible compared to process spawning costs (~10–50 milliseconds), making the trade-off unjustified. - **Per-workload hash tracking**: Track individual workload mutations via content hashes to selectively skip conversion of unchanged workloads. Adds protocol complexity and statefulness without meaningful gain, since the entire message must still be transferred and decoded to extract the hashes. +#### Server uses a single communication listen endpoint +`swdd~server-uses-single-communication-listen-endpoint~1` + +Status: approved + +The Ankaios server shall allow configuring exactly one communication middleware listen endpoint. + +Rationale: +Keeping a single active endpoint keeps configuration and operations simple. + +Needs: +- impl + +Assumptions: + +- Standard deployments use an IP endpoint with mTLS expected. +- Unix domain sockets are mainly used for local development, and access shall be limited to selected users or groups on the same system. + +Considered alternatives: + +- **Support multiple endpoint types and multiple simultaneous listeners:** Rejected to avoid additional validation, runtime complexity, and maintenance cost. + ## Structural view The following diagram shows the structural view of the Ankaios Server: @@ -325,6 +347,82 @@ Needs: - impl - itest +#### Server supports unix domain socket endpoints +`swdd~server-supports-unix-domain-socket-endpoints~1` + +Status: approved + +The Ankaios server shall support configuring the gRPC communication middleware to listen on either a TCP socket endpoint (`:`) or a Unix domain socket endpoint (`unix://`). + +Rationale: +Unix domain sockets can be used for local inter-process communication with filesystem-based user and group access control. + +Tags: +- AnkaiosServer +- Communication Middleware + +Needs: +- impl +- utest +- itest + +#### Server validates unix domain socket configuration +`swdd~server-validates-unix-domain-socket-configuration~1` + +Status: approved + +When the Ankaios server is configured with a Unix domain socket endpoint (`unix://`), the server shall reject startup configuration that: + +- enables `insecure` +- sets TLS certificate paths or TLS certificate content fields + +Rationale: +Unix domain socket communication uses local filesystem permissions and does not require TLS transport settings. + +Tags: +- AnkaiosServer +- Communication Middleware + +Needs: +- impl +- utest + +#### Server validates socket group endpoint compatibility +`swdd~server-validates-socket-group-endpoint-compatibility~1` + +Status: approved + +When the Ankaios server is configured with a non-Unix endpoint, the server shall reject startup configuration that sets `socket_group`. + +Rationale: +The `socket_group` setting applies only to filesystem permissions of Unix domain socket files and is not applicable to TCP endpoints. + +Tags: +- AnkaiosServer +- Communication Middleware + +Needs: +- impl +- utest + +#### Server configures unix domain socket group +`swdd~server-configures-unix-domain-socket-group~1` + +Status: approved + +When the Ankaios server is configured with a Unix domain socket endpoint and a `socket_group`, the server shall set the group ownership of the created socket file to that group. + +Rationale: +The `socket_group` setting allows controlling local access to the Unix domain socket through group permissions. + +Tags: +- AnkaiosServer +- Communication Middleware + +Needs: +- impl +- utest + #### Server uses common async communication channels `swdd~server-uses-async-channels~1` diff --git a/server/src/cli.rs b/server/src/cli.rs index 665a6883e..1ff4d4840 100644 --- a/server/src/cli.rs +++ b/server/src/cli.rs @@ -13,7 +13,7 @@ // SPDX-License-Identifier: Apache-2.0 use clap::{ArgAction, Parser}; -use std::{env, net::SocketAddr}; +use std::env; pub fn parse() -> Arguments { Arguments::parse() @@ -39,9 +39,14 @@ pub struct Arguments { /// The path to the server config file. /// The default path is /etc/ankaios/ank-server.conf pub config_path: Option, - #[arg(required = false, short = 'a', long = "address")] - /// The address, including the port, the server shall listen at [default: 127.0.0.1:25551]. - pub addr: Option, + #[arg(required = false, short = 'a', long = "address", env = "ANKSERVER_SERVER_URL")] + /// The endpoint the server shall listen at [default: 127.0.0.1:25551]. + /// Supported values are host:port (TCP) and unix:///path/to/socket (Unix domain socket). + pub addr: Option, + #[arg(long = "socket-group", env = "ANKSERVER_SOCKET_GROUP")] + /// Group name assigned to the Unix domain socket file. + /// This option is only valid with unix:// server addresses. + pub socket_group: Option, #[arg(short = 'k', long = "insecure", action=ArgAction::Set, num_args=0, default_missing_value="true", env = "ANKSERVER_INSECURE")] /// Flag to disable TLS communication between Ankaios server, agent and ank CLI. pub insecure: Option, diff --git a/server/src/main.rs b/server/src/main.rs index a9f28ead7..304bb9b0f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -20,6 +20,7 @@ use ankaios_api::ank_base::{CompleteStateSpec, StateSpec, validate_tags}; use ankaios_server::{AnkaiosServer, create_from_server_channel, create_to_server_channel}; use common::communications_server::CommunicationsServer; +use common::communications_server::ServerConnection; use common::config::handle_config; use common::std_extensions::GracefulExitResult; @@ -66,10 +67,10 @@ async fn main() { server_config .update_with_args(&args) - .unwrap_or_exit("Failed to load certificate files!"); + .unwrap_or_exit("Failed to apply server configuration!"); log::debug!( - "Starting the Ankaios server with \n\tserver address: '{}', \n\tstartup manifest path: '{}'", + "Starting the Ankaios server with \n\tserver endpoint: '{}', \n\tstartup manifest path: '{}'", server_config.address, server_config .startup_manifest @@ -106,30 +107,34 @@ async fn main() { let (to_server, server_receiver) = create_to_server_channel(common::CHANNEL_CAPACITY); let (to_agents, agents_receiver) = create_from_server_channel(common::CHANNEL_CAPACITY); - if let Err(err_message) = TLSConfig::is_config_conflicting( - server_config.insecure, - &server_config.ca_pem_content, - &server_config.crt_pem_content, - &server_config.key_pem_content, - ) { - log::warn!("{err_message}"); - } + let tls_config = match &server_config.address { + ServerConnection::Tcp(_) => { + if let Err(err_message) = TLSConfig::is_config_conflicting( + server_config.insecure, + &server_config.ca_pem_content, + &server_config.crt_pem_content, + &server_config.key_pem_content, + ) { + log::warn!("{err_message}"); + } - // [impl->swdd~server-establishes-insecure-communication-based-on-provided-insecure-cli-argument~1] - // [impl->swdd~server-provides-file-paths-to-communication-middleware~1] - // [impl->swdd~server-fails-on-missing-file-paths-and-insecure-cli-arguments~1] - let tls_config = TLSConfig::new( - server_config.insecure, - server_config.ca_pem_content, - server_config.crt_pem_content, - server_config.key_pem_content, - ); + // [impl->swdd~server-establishes-insecure-communication-based-on-provided-insecure-cli-argument~1] + // [impl->swdd~server-provides-file-paths-to-communication-middleware~1] + // [impl->swdd~server-fails-on-missing-file-paths-and-insecure-cli-arguments~1] + TLSConfig::new( + server_config.insecure, + server_config.ca_pem_content, + server_config.crt_pem_content, + server_config.key_pem_content, + ) + .unwrap_or_exit("Missing certificate files") + } + // [impl->swdd~server-validates-unix-domain-socket-configuration~1] + ServerConnection::Unix(_) => None, + }; - let mut communications_server = GRPCCommunicationsServer::new( - to_server, - // [impl->swdd~server-fails-on-missing-file-paths-and-insecure-cli-arguments~1] - tls_config.unwrap_or_exit("Missing certificate files"), - ); + let mut communications_server = GRPCCommunicationsServer::new(to_server, tls_config) + .with_unix_socket_group(server_config.socket_group.clone()); let mut server = AnkaiosServer::new( server_receiver, @@ -139,6 +144,7 @@ async fn main() { tokio::select! { // [impl->swdd~server-default-communication-grpc~1] + // [impl->swdd~server-supports-unix-domain-socket-endpoints~1] communication_result = communications_server.start(agents_receiver, server_config.address) => { communication_result.unwrap_or_exit("server error") } diff --git a/server/src/server_config.rs b/server/src/server_config.rs index 0f2ddda48..6cc3a3dc7 100644 --- a/server/src/server_config.rs +++ b/server/src/server_config.rs @@ -14,6 +14,7 @@ use crate::cli::Arguments; use common::DEFAULT_SOCKET_ADDRESS; +use common::communications_server::ServerConnection; use common::config::{CONFIG_VERSION, ConfigFile, ConversionErrors}; use common::std_extensions::{UnreachableOption, UnreachableResult}; @@ -27,27 +28,52 @@ use tests::read_pem_file; use serde::{Deserialize, Deserializer}; use std::fs::read_to_string; use std::net::SocketAddr; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use toml::from_str; pub const DEFAULT_SERVER_CONFIG_FILE_PATH: [&str; 1] = ["/etc/ankaios/ank-server.conf"]; pub const DEFAULT_MUTATING_HOOKS_DIR: &str = "/usr/libexec/ankaios/hooks"; -pub fn get_default_address() -> SocketAddr { - DEFAULT_SOCKET_ADDRESS.parse().unwrap_or_unreachable() +pub fn get_default_address() -> ServerConnection { + ServerConnection::Tcp(DEFAULT_SOCKET_ADDRESS.parse().unwrap_or_unreachable()) } fn get_default_mutating_hooks_path() -> PathBuf { PathBuf::from(DEFAULT_MUTATING_HOOKS_DIR) } -fn convert_to_socket_address<'de, D>(deserializer: D) -> Result +fn parse_server_address(value: &str) -> Result { + // [impl->swdd~server-uses-single-communication-listen-endpoint~1] + // [impl->swdd~server-supports-unix-domain-socket-endpoints~1] + if let Some(path) = value.strip_prefix("unix://") { + if path.is_empty() { + return Err("Unix domain socket path cannot be empty".to_string()); + } + + if !Path::new(path).is_absolute() { + return Err(format!("Unix domain socket path '{path}' must be absolute")); + } + + return Ok(ServerConnection::Unix(PathBuf::from(path))); + } + + value + .parse::() + .map(ServerConnection::Tcp) + .map_err(|err| { + format!( + "Invalid server endpoint '{value}': expected host:port or unix:///path/to/socket ({err})" + ) + }) +} + +fn convert_to_server_address<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let s: String = Deserialize::deserialize(deserializer)?; - s.parse::().map_err(serde::de::Error::custom) + parse_server_address(&s).map_err(serde::de::Error::custom) } // [impl->swdd~server-config-supports-mutating-hooks~1] @@ -65,11 +91,13 @@ pub struct MutatingHook { pub struct ServerConfig { pub version: String, pub startup_manifest: Option, - #[serde(deserialize_with = "convert_to_socket_address")] + #[serde(deserialize_with = "convert_to_server_address")] #[serde(default = "get_default_address")] - pub address: SocketAddr, + pub address: ServerConnection, #[serde(default)] pub insecure: bool, + #[serde(default)] + pub socket_group: Option, ca_pem: Option, crt_pem: Option, key_pem: Option, @@ -87,6 +115,7 @@ impl Default for ServerConfig { startup_manifest: None, address: get_default_address(), insecure: false, + socket_group: None, ca_pem: None, crt_pem: None, key_pem: None, @@ -118,6 +147,10 @@ impl ConfigFile for ServerConfig { )); } + // [impl->swdd~server-validates-unix-domain-socket-configuration~1] + // [impl->swdd~server-validates-socket-group-endpoint-compatibility~1] + validate_socket_configuration(&server_config).map_err(ConversionErrors::InvalidConfig)?; + if let Some(ca_pem_path) = &server_config.ca_pem { let ca_pem_content = read_pem_file(ca_pem_path, PemFileType::Certificate) .map_err(|err| ConversionErrors::InvalidCertificate(err.to_string()))?; @@ -145,13 +178,18 @@ impl ServerConfig { } if let Some(addr) = &args.addr { - self.address = *addr; + // [impl->swdd~server-supports-unix-domain-socket-endpoints~1] + self.address = parse_server_address(addr)?; } if let Some(insecure) = args.insecure { self.insecure = insecure; } + if let Some(socket_group) = &args.socket_group { + self.socket_group = Some(socket_group.to_owned()); + } + if let Some(ca_pem_path) = &args.ca_pem { self.ca_pem = Some(ca_pem_path.to_owned()); let ca_pem_content = read_pem_file(ca_pem_path, PemFileType::Certificate) @@ -170,10 +208,61 @@ impl ServerConfig { .map_err(|err| err.to_string())?; self.key_pem_content = Some(key_pem_content); } + + // [impl->swdd~server-validates-unix-domain-socket-configuration~1] + // [impl->swdd~server-validates-socket-group-endpoint-compatibility~1] + validate_socket_configuration(self)?; + Ok(()) } } +fn validate_socket_configuration(server_config: &ServerConfig) -> Result<(), String> { + match &server_config.address { + // [impl->swdd~server-validates-unix-domain-socket-configuration~1] + ServerConnection::Unix(_) => { + if server_config.insecure { + return Err( + "Invalid server config: 'insecure' must not be enabled for unix:// endpoints" + .to_string(), + ); + } + + if server_config.ca_pem.is_some() + || server_config.crt_pem.is_some() + || server_config.key_pem.is_some() + || server_config.ca_pem_content.is_some() + || server_config.crt_pem_content.is_some() + || server_config.key_pem_content.is_some() + { + return Err( + "Invalid server config: TLS certificate settings are not allowed for unix:// endpoints" + .to_string(), + ); + } + + if let Some(group) = server_config.socket_group.as_deref() + && group.trim().is_empty() + { + return Err( + "Invalid server config: 'socket_group' must not be empty when set".to_string(), + ); + } + } + ServerConnection::Tcp(_) => { + // [impl->swdd~server-validates-socket-group-endpoint-compatibility~1] + if server_config.socket_group.is_some() { + return Err( + "Invalid server config: 'socket_group' is only supported for unix:// endpoints" + .to_string(), + ); + } + } + } + + Ok(()) +} + ////////////////////////////////////////////////////////////////////////////// // ######## ####### ######### ######### // // ## ## ## ## // @@ -190,6 +279,7 @@ mod tests { use ankaios_api::test_utils::fixtures; use common::DEFAULT_SOCKET_ADDRESS; + use common::communications_server::ServerConnection; use common::config::{ConfigFile, ConversionErrors}; use std::io::Write; @@ -232,7 +322,7 @@ mod tests { assert_eq!( default_server_config.address, - DEFAULT_SOCKET_ADDRESS.parse::().unwrap() + ServerConnection::Tcp(DEFAULT_SOCKET_ADDRESS.parse::().unwrap()) ); assert!(!default_server_config.insecure); assert_eq!(default_server_config.version, "v1"); @@ -289,7 +379,8 @@ mod tests { let args = Arguments { manifest_path: Some(STARTUP_MANIFEST_PATH.to_string()), config_path: Some(DEFAULT_SERVER_CONFIG_FILE_PATH[0].to_string()), - addr: TEST_SOCKET_ADDRESS.parse::().ok(), + addr: Some(TEST_SOCKET_ADDRESS.to_string()), + socket_group: None, insecure: Some(false), ca_pem: Some(fixtures::CA_PEM_PATH.to_string()), crt_pem: Some(fixtures::CRT_PEM_PATH.to_string()), @@ -304,7 +395,7 @@ mod tests { ); assert_eq!( server_config.address, - TEST_SOCKET_ADDRESS.parse::().unwrap() + ServerConnection::Tcp(TEST_SOCKET_ADDRESS.parse::().unwrap()) ); assert!(!server_config.insecure); assert_eq!( @@ -356,7 +447,8 @@ mod tests { let args = Arguments { manifest_path: Some(STARTUP_MANIFEST_PATH.to_string()), config_path: Some(DEFAULT_SERVER_CONFIG_FILE_PATH[0].to_string()), - addr: TEST_SOCKET_ADDRESS.parse::().ok(), + addr: Some(TEST_SOCKET_ADDRESS.to_string()), + socket_group: None, insecure: Some(false), ca_pem: None, crt_pem: None, @@ -450,7 +542,7 @@ mod tests { assert_eq!( server_config.address, - "127.0.0.1:25551".parse::().unwrap() + ServerConnection::Tcp("127.0.0.1:25551".parse::().unwrap()) ); assert_eq!( server_config.ca_pem_content, @@ -547,4 +639,152 @@ mod tests { assert!(server_config.mutating_hooks.is_empty()); } + + // [utest->swdd~server-loads-config-file~2] + // [utest->swdd~server-supports-unix-domain-socket-endpoints~1] + #[test] + fn utest_server_config_from_file_with_unix_domain_socket() { + let server_config_content = r"# + version = 'v1' + address = 'unix:///tmp/ankaios-server.sock' + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{server_config_content}").unwrap(); + + let server_config = ServerConfig::from_file(PathBuf::from(tmp_config_file.path())).unwrap(); + + assert_eq!( + server_config.address, + ServerConnection::Unix(PathBuf::from("/tmp/ankaios-server.sock")) + ); + } + + // [utest->swdd~server-loads-config-file~2] + // [utest->swdd~server-supports-unix-domain-socket-endpoints~1] + #[test] + fn utest_server_config_update_with_args_unix_domain_socket() { + let mut server_config = ServerConfig::default(); + let args = Arguments { + manifest_path: None, + config_path: None, + addr: Some("unix:///tmp/ankaios-from-args.sock".to_string()), + socket_group: None, + insecure: None, + ca_pem: None, + crt_pem: None, + key_pem: None, + }; + + server_config.update_with_args(&args).unwrap(); + + assert_eq!( + server_config.address, + ServerConnection::Unix(PathBuf::from("/tmp/ankaios-from-args.sock")) + ); + } + + // [utest->swdd~server-validates-unix-domain-socket-configuration~1] + #[test] + fn utest_server_config_rejects_insecure_with_unix_domain_socket() { + let server_config_content = r"# + version = 'v1' + address = 'unix:///tmp/ankaios-server.sock' + insecure = true + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{server_config_content}").unwrap(); + + let result = ServerConfig::from_file(PathBuf::from(tmp_config_file.path())); + + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } + + // [utest->swdd~server-validates-unix-domain-socket-configuration~1] + #[test] + fn utest_server_config_rejects_tls_settings_with_unix_domain_socket() { + let server_config_content = r"# + version = 'v1' + address = 'unix:///tmp/ankaios-server.sock' + ca_pem = '/tmp/.certs/ca.pem' + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{server_config_content}").unwrap(); + + let result = ServerConfig::from_file(PathBuf::from(tmp_config_file.path())); + + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } + + // [utest->swdd~server-validates-socket-group-endpoint-compatibility~1] + #[test] + fn utest_server_config_rejects_socket_group_with_tcp_address() { + let server_config_content = r"# + version = 'v1' + address = '127.0.0.1:25551' + socket_group = 'ankaios' + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{server_config_content}").unwrap(); + + let result = ServerConfig::from_file(PathBuf::from(tmp_config_file.path())); + + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } + + // [utest->swdd~server-configures-unix-domain-socket-group~1] + #[test] + fn utest_server_config_accepts_socket_group_with_unix_address() { + let server_config_content = r"# + version = 'v1' + address = 'unix:///tmp/ankaios-server.sock' + socket_group = 'ankaios' + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{server_config_content}").unwrap(); + + let server_config = ServerConfig::from_file(PathBuf::from(tmp_config_file.path())).unwrap(); + + assert_eq!(server_config.socket_group, Some("ankaios".to_string())); + } + + // [utest->swdd~server-supports-unix-domain-socket-endpoints~1] + #[test] + fn utest_server_config_rejects_relative_unix_domain_socket_path_from_file() { + let server_config_content = r"# + version = 'v1' + address = 'unix://tmp/ankaios-server.sock' + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{server_config_content}").unwrap(); + + let result = ServerConfig::from_file(PathBuf::from(tmp_config_file.path())); + + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } + + // [utest->swdd~server-supports-unix-domain-socket-endpoints~1] + #[test] + fn utest_server_config_rejects_relative_unix_domain_socket_path_from_args() { + let mut server_config = ServerConfig::default(); + let args = Arguments { + manifest_path: None, + config_path: None, + addr: Some("unix://tmp/ankaios-from-args.sock".to_string()), + socket_group: None, + insecure: None, + ca_pem: None, + crt_pem: None, + key_pem: None, + }; + + let result = server_config.update_with_args(&args); + + assert!(result.is_err()); + } } From 676fa0a1193b794cd3e94edba19bdf762b66704e Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:01:11 +0200 Subject: [PATCH 10/55] Add domain socket support to ank-agent --- agent/config/ank-agent.conf | 7 +- agent/src/agent_config.rs | 119 +++++++++++++++++++---- agent/src/cli.rs | 13 ++- agent/src/main.rs | 48 +++++----- common/src/communications_server.rs | 19 +++- grpc/src/client.rs | 120 +++++++++++++++++++++--- grpc/tests/grpc_test.rs | 140 +++++++++++++++++++++++----- 7 files changed, 385 insertions(+), 81 deletions(-) diff --git a/agent/config/ank-agent.conf b/agent/config/ank-agent.conf index 15e3f0680..73b34b73d 100644 --- a/agent/config/ank-agent.conf +++ b/agent/config/ank-agent.conf @@ -10,8 +10,8 @@ version = 'v1' # (a-z and A-Z), numbers and the symbols "-" and "_". name = 'agent_A' -# The server url. -# server_url = 'https://127.0.0.1:25551' +# The server endpoint. +address = 'unix:///run/ankaios/server.sock' # An existing path where to manage the fifo files. # If not set, defaults to '$TMPDIR/ankaios/' (falls back to '/tmp/ankaios/' if TMPDIR is not set). @@ -21,7 +21,7 @@ name = 'agent_A' # The flag to disable TLS communication with the server. # If set to 'true', then the agent shall not use TLS. -insecure = true +# This option must not be used with unix:// addresses. # The list of enabled runtimes. If the array is missing # all runtimes will be enabled. @@ -31,6 +31,7 @@ insecure = true # tags = { "cpu" = "x86_64", "location" = "cloud", "camera" = "available" } # The path to the ca certificate pem file. +# TLS certificate options are only valid with TCP addresses. # ca_pem = '/etc/ankaios/certs/ca.pem' # The path to agent certificate pem file. diff --git a/agent/src/agent_config.rs b/agent/src/agent_config.rs index ec1b13f76..67ca00ef8 100644 --- a/agent/src/agent_config.rs +++ b/agent/src/agent_config.rs @@ -29,6 +29,7 @@ use tests::read_pem_file; use serde::Deserialize; use std::collections::HashMap; use std::fs::read_to_string; +use std::path::Path; use std::path::PathBuf; use toml::from_str; @@ -38,14 +39,38 @@ pub fn get_default_url() -> String { DEFAULT_SERVER_ADDRESS.to_string() } +fn is_unix_address(address: &str) -> bool { + address.starts_with("unix://") +} + +fn validate_address_format(address: &str) -> Result<(), String> { + if let Some(path) = address.strip_prefix("unix://") { + if path.is_empty() { + return Err("Wrong server address format: empty unix socket path.".to_string()); + } + if !Path::new(path).is_absolute() { + return Err(format!( + "Wrong server address format: unix socket path must be absolute: '{address}'." + )); + } + return Ok(()); + } + + if address.starts_with("https://") || address.starts_with("http://") { + Ok(()) + } else { + Err(format!("Wrong server address format: '{address}'.")) + } +} + // [impl->swdd~agent-loads-config-file~2] #[derive(Debug, Deserialize, PartialEq)] pub struct AgentConfig { pub version: String, #[serde(default)] pub name: String, - #[serde(default = "get_default_url")] - pub server_url: String, + #[serde(default = "get_default_url", alias = "server_url")] + pub address: String, #[serde(default = "crate::io_utils::default_run_folder_string")] pub run_folder: String, #[serde(default)] @@ -67,7 +92,7 @@ impl Default for AgentConfig { AgentConfig { version: CONFIG_VERSION.to_string(), name: String::new(), - server_url: get_default_url(), + address: get_default_url(), run_folder: default_run_folder_string(), insecure: bool::default(), runtimes: None, @@ -119,6 +144,10 @@ impl ConfigFile for AgentConfig { agent_config.key_pem_content = Some(key_pem_content); } + validate_address_format(&agent_config.address).map_err(ConversionErrors::InvalidConfig)?; + + validate_socket_configuration(&agent_config).map_err(ConversionErrors::InvalidConfig)?; + Ok(agent_config) } } @@ -129,8 +158,8 @@ impl AgentConfig { self.name = name.to_string(); } - if let Some(url) = &args.server_url { - self.server_url = url.to_string(); + if let Some(address) = &args.address { + self.address = address.to_string(); } if let Some(run_folder) = &args.run_folder { @@ -163,10 +192,41 @@ impl AgentConfig { if let Some(tags) = &args.tags { self.tags = tags.iter().cloned().collect(); } + + validate_address_format(&self.address)?; + validate_socket_configuration(self)?; + Ok(()) } } +fn validate_socket_configuration(agent_config: &AgentConfig) -> Result<(), String> { + // keep behavior aligned with server validation for unix:// endpoints + if is_unix_address(&agent_config.address) { + if agent_config.insecure { + return Err( + "Invalid agent config: 'insecure' must not be enabled for unix:// endpoints" + .to_string(), + ); + } + + if agent_config.ca_pem.is_some() + || agent_config.crt_pem.is_some() + || agent_config.key_pem.is_some() + || agent_config.ca_pem_content.is_some() + || agent_config.crt_pem_content.is_some() + || agent_config.key_pem_content.is_some() + { + return Err( + "Invalid agent config: TLS certificate settings are not allowed for unix:// endpoints" + .to_string(), + ); + } + } + + Ok(()) +} + ////////////////////////////////////////////////////////////////////////////// // ######## ####### ######### ######### // // ## ## ## ## // @@ -219,7 +279,7 @@ mod tests { let default_agent_config = AgentConfig::default(); assert_eq!( - default_agent_config.server_url, + default_agent_config.address, DEFAULT_SERVER_ADDRESS.to_string() ); assert!(!default_agent_config.insecure); @@ -278,7 +338,7 @@ mod tests { let args = Arguments { config_path: None, agent_name: Some(fixtures::AGENT_NAMES[0].to_string()), - server_url: Some(DEFAULT_SERVER_ADDRESS.to_string()), + address: Some(DEFAULT_SERVER_ADDRESS.to_string()), run_folder: Some(default_run_folder.clone()), insecure: Some(false), tags: None, @@ -290,13 +350,10 @@ mod tests { agent_config.update_with_args(&args).unwrap(); assert_eq!(agent_config.name, fixtures::AGENT_NAMES[0].to_string()); - assert_eq!(agent_config.server_url, DEFAULT_SERVER_ADDRESS.to_string()); + assert_eq!(agent_config.address, DEFAULT_SERVER_ADDRESS.to_string()); assert_eq!(agent_config.run_folder, default_run_folder); assert!(!agent_config.insecure); - assert_eq!( - agent_config.ca_pem, - Some(fixtures::CA_PEM_PATH.to_string()) - ); + assert_eq!(agent_config.ca_pem, Some(fixtures::CA_PEM_PATH.to_string())); assert_eq!( agent_config.crt_pem, Some(fixtures::CRT_PEM_PATH.to_string()) @@ -343,7 +400,7 @@ mod tests { let args = Arguments { config_path: None, agent_name: Some(fixtures::AGENT_NAMES[0].to_string()), - server_url: Some(DEFAULT_SERVER_ADDRESS.to_string()), + address: Some(DEFAULT_SERVER_ADDRESS.to_string()), run_folder: Some(default_run_folder), insecure: Some(false), tags: None, @@ -376,7 +433,7 @@ mod tests { r"# version = 'v1' name = '{}' - server_url = 'https://127.0.0.1:25551' + address = 'https://127.0.0.1:25551' run_folder = '{}' insecure = true ca_pem_content = '''{}''' @@ -400,7 +457,7 @@ mod tests { let agent_config = agent_config_res.unwrap(); assert_eq!(agent_config.name, fixtures::AGENT_NAMES[0].to_string()); - assert_eq!(agent_config.server_url, DEFAULT_SERVER_ADDRESS.to_string()); + assert_eq!(agent_config.address, DEFAULT_SERVER_ADDRESS.to_string()); assert_eq!(agent_config.run_folder, default_run_folder_string()); assert!(agent_config.insecure); assert_eq!( @@ -428,7 +485,7 @@ mod tests { let args = Arguments { config_path: None, agent_name: None, - server_url: None, + address: None, run_folder: None, insecure: None, ca_pem: None, @@ -446,4 +503,34 @@ mod tests { ); assert!(!agent_config.tags.contains_key("config_tag")); } + + #[test] + fn utest_agent_config_rejects_insecure_with_unix_domain_socket() { + let agent_config_content = r"# + version = 'v1' + address = 'unix:///tmp/ankaios-agent.sock' + insecure = true + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{agent_config_content}").unwrap(); + + let result = AgentConfig::from_file(PathBuf::from(tmp_config_file.path())); + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } + + #[test] + fn utest_agent_config_rejects_tls_with_unix_domain_socket() { + let agent_config_content = r"# + version = 'v1' + address = 'unix:///tmp/ankaios-agent.sock' + ca_pem = '/tmp/.certs/ca.pem' + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{agent_config_content}").unwrap(); + + let result = AgentConfig::from_file(PathBuf::from(tmp_config_file.path())); + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } } diff --git a/agent/src/cli.rs b/agent/src/cli.rs index 67e556a50..c203bfa3b 100644 --- a/agent/src/cli.rs +++ b/agent/src/cli.rs @@ -31,9 +31,16 @@ pub struct Arguments { /// The name to use for the registration with the server. Every agent has to register with a unique name. /// Agent name shall contain only regular upper and lowercase characters (a-z and A-Z), numbers and the symbols "-" and "_". pub agent_name: Option, - #[arg(short = 's', long = "server-url", required = false)] - /// The server url. - pub server_url: Option, + #[arg( + short = 's', + long = "address", + alias = "server-url", + required = false, + env = "ANKAGENT_SERVER_URL" + )] + /// The server endpoint. + /// Supported values are https://host:port and unix:///path/to/socket. + pub address: Option, #[arg(short = 'r', long = "run-folder", required = false)] /// An existing directory where agent specific runtime files will be stored. If not specified, a default folder is created. /// If the directory already exists, it must be owned by the user running the agent and not be accessible by other users or groups. diff --git a/agent/src/main.rs b/agent/src/main.rs index da2a9031d..a4745af5b 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -131,9 +131,9 @@ async fn main() { .unwrap_or_exit("Error encountered while checking agent name!"); log::debug!( - "Starting the Ankaios agent with \n\tname: '{}', \n\tserver url: '{}', \n\trun directory: '{}'", + "Starting the Ankaios agent with \n\tname: '{}', \n\tserver endpoint: '{}', \n\trun directory: '{}'", agent_config.name, - agent_config.server_url, + agent_config.address, agent_config.run_folder, ); @@ -192,31 +192,35 @@ async fn main() { workload_state_sender, ); - if let Err(err_message) = TLSConfig::is_config_conflicting( - agent_config.insecure, - &agent_config.ca_pem_content, - &agent_config.crt_pem_content, - &agent_config.key_pem_content, - ) { - log::warn!("{err_message}"); - } + let tls_config = if agent_config.address.starts_with("unix://") { + None + } else { + if let Err(err_message) = TLSConfig::is_config_conflicting( + agent_config.insecure, + &agent_config.ca_pem_content, + &agent_config.crt_pem_content, + &agent_config.key_pem_content, + ) { + log::warn!("{err_message}"); + } - // [impl->swdd~agent-establishes-insecure-communication-based-on-provided-insecure-cli-argument~1] - // [impl->swdd~agent-provides-file-paths-to-communication-middleware~1] - // [impl->swdd~agent-fails-on-missing-file-paths-and-insecure-cli-arguments~1] - let tls_config = TLSConfig::new( - agent_config.insecure, - agent_config.ca_pem_content, - agent_config.crt_pem_content, - agent_config.key_pem_content, - ); + // [impl->swdd~agent-establishes-insecure-communication-based-on-provided-insecure-cli-argument~1] + // [impl->swdd~agent-provides-file-paths-to-communication-middleware~1] + // [impl->swdd~agent-fails-on-missing-file-paths-and-insecure-cli-arguments~1] + TLSConfig::new( + agent_config.insecure, + agent_config.ca_pem_content, + agent_config.crt_pem_content, + agent_config.key_pem_content, + ) + .unwrap_or_exit("Missing certificate files") + }; let mut communications_client = GRPCCommunicationsClient::new_agent_communication( agent_config.name.clone(), - agent_config.server_url, + agent_config.address, agent_config.tags, - // [impl->swdd~agent-fails-on-missing-file-paths-and-insecure-cli-arguments~1] - tls_config.unwrap_or_exit("Missing certificate files"), + tls_config, ) .unwrap_or_exit("Failed to create communications client."); diff --git a/common/src/communications_server.rs b/common/src/communications_server.rs index bab6deebe..ee7c895f5 100644 --- a/common/src/communications_server.rs +++ b/common/src/communications_server.rs @@ -17,7 +17,24 @@ use crate::{ }; use async_trait::async_trait; +use std::fmt; use std::net::SocketAddr; +use std::path::PathBuf; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ServerConnection { + Tcp(SocketAddr), + Unix(PathBuf), +} + +impl fmt::Display for ServerConnection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ServerConnection::Tcp(addr) => write!(f, "{addr}"), + ServerConnection::Unix(path) => write!(f, "unix://{}", path.display()), + } + } +} // [impl->swdd~common-interface-definitions~1] #[async_trait] @@ -25,6 +42,6 @@ pub trait CommunicationsServer { async fn start( &mut self, mut receiver: FromServerReceiver, - addr: SocketAddr, + addr: ServerConnection, ) -> Result<(), CommunicationMiddlewareError>; } diff --git a/grpc/src/client.rs b/grpc/src/client.rs index ec00eef43..f0b3fbd0b 100644 --- a/grpc/src/client.rs +++ b/grpc/src/client.rs @@ -29,12 +29,16 @@ use common::std_extensions::IllegalStateResult; use common::to_server_interface::ToServerReceiver; use async_trait::async_trait; +use hyper_util::rt::TokioIo; use regex::Regex; use std::collections::HashMap; +use std::path::PathBuf; +use tokio::net::UnixStream; use tokio::select; use tokio::sync::mpsc::{self, Receiver}; use tokio_stream::wrappers::ReceiverStream; -use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity}; +use tonic::transport::{Certificate, Channel, ClientTlsConfig, Endpoint, Identity}; +use tower::service_fn; const RECONNECT_TIMEOUT_SECONDS: u64 = 1; @@ -43,9 +47,14 @@ enum ConnectionType { Cli, } +enum ServerEndpoint { + Tcp(String), + Unix(PathBuf), +} + pub struct GRPCCommunicationsClient { name: String, - server_address: String, + server_endpoint: ServerEndpoint, connection_type: ConnectionType, tags: HashMap, tls_config: Option, @@ -59,14 +68,36 @@ fn get_server_url(server_address: &str, tls_config: &Option) -> Strin } } -fn verify_address_format(server_address: &String) -> Result<(), CommunicationMiddlewareError> { +fn parse_server_endpoint( + server_address: &String, + tls_config: &Option, +) -> Result { + if let Some(path) = server_address.strip_prefix("unix://") { + if path.is_empty() { + return Err(CommunicationMiddlewareError( + "Wrong server address format: empty unix socket path.".to_string(), + )); + } + if tls_config.is_some() { + return Err(CommunicationMiddlewareError( + "Invalid server address and TLS configuration: unix:// endpoints do not support TLS settings." + .to_string(), + )); + } + return Ok(ServerEndpoint::Unix(PathBuf::from(path))); + } + let re = Regex::new(r"^https?:\/\/.+").unwrap_or_illegal_state(); if !re.is_match(server_address) { return Err(CommunicationMiddlewareError(format!( "Wrong server address format: '{server_address}'." ))); } - Ok(()) + + Ok(ServerEndpoint::Tcp(get_server_url( + server_address, + tls_config, + ))) } impl GRPCCommunicationsClient { @@ -76,11 +107,11 @@ impl GRPCCommunicationsClient { tags: HashMap, tls_config: Option, ) -> Result { - verify_address_format(&server_address)?; + let server_endpoint = parse_server_endpoint(&server_address, &tls_config)?; Ok(Self { name, - server_address: get_server_url(&server_address, &tls_config), + server_endpoint, connection_type: ConnectionType::Agent, tags, tls_config, @@ -92,11 +123,11 @@ impl GRPCCommunicationsClient { server_address: String, tls_config: Option, ) -> Result { - verify_address_format(&server_address)?; + let server_endpoint = parse_server_endpoint(&server_address, &tls_config)?; Ok(Self { name, - server_address: get_server_url(&server_address, &tls_config), + server_endpoint, connection_type: ConnectionType::Cli, tags: HashMap::new(), tls_config, @@ -138,7 +169,7 @@ impl CommunicationsClient for GRPCCommunicationsClient { log::debug!("No connection to the server: '{err}'"); return Err(CommunicationMiddlewareError(format!( "Could not connect to Ankaios server on '{}'.", - self.server_address + self.server_endpoint ))); } // [impl->swdd~grpc-client-outputs-error-server-connection-loss-for-cli-connection~1] @@ -247,7 +278,16 @@ impl GRPCCommunicationsClient { .ca_certificate(ca) .identity(client_identity); - let channel = Channel::from_shared(self.server_address.to_string()) + let tcp_endpoint = match &self.server_endpoint { + ServerEndpoint::Tcp(endpoint) => endpoint, + ServerEndpoint::Unix(_) => { + return Err(GrpcMiddlewareError::ConnectionInterrupted( + "TLS is not supported for unix:// endpoints".to_string(), + )); + } + }; + + let channel = Channel::from_shared(tcp_endpoint.to_string()) .map_err(|err| GrpcMiddlewareError::TLSError(err.to_string()))? .tls_config(tls)? .connect() @@ -262,8 +302,24 @@ impl GRPCCommunicationsClient { } // [impl->swdd~grpc-agent-deactivate-mtls-when-no-certificates-and-no-key-provided-upon-start~1] None => { - let mut client = - AgentConnectionClient::connect(self.server_address.to_string()).await?; + let mut client = match &self.server_endpoint { + ServerEndpoint::Tcp(endpoint) => { + AgentConnectionClient::connect(endpoint.to_string()).await? + } + ServerEndpoint::Unix(path) => { + let path = path.clone(); + let channel = Endpoint::try_from("http://[::]:50061") + .map_err(|err| { + GrpcMiddlewareError::ConnectionInterrupted(err.to_string()) + })? + .connect_with_connector(service_fn(move |_| { + let path = path.clone(); + async move { UnixStream::connect(path).await.map(TokioIo::new) } + })) + .await?; + AgentConnectionClient::new(channel) + } + }; let res = client .connect_agent(ReceiverStream::new(grpc_rx)) @@ -289,7 +345,16 @@ impl GRPCCommunicationsClient { .ca_certificate(ca) .identity(client_identity); - let channel = Channel::from_shared(self.server_address.to_string()) + let tcp_endpoint = match &self.server_endpoint { + ServerEndpoint::Tcp(endpoint) => endpoint, + ServerEndpoint::Unix(_) => { + return Err(GrpcMiddlewareError::ConnectionInterrupted( + "TLS is not supported for unix:// endpoints".to_string(), + )); + } + }; + + let channel = Channel::from_shared(tcp_endpoint.to_string()) .map_err(|err| GrpcMiddlewareError::TLSError(err.to_string()))? .tls_config(tls)? .connect() @@ -305,8 +370,24 @@ impl GRPCCommunicationsClient { } // [impl->swdd~grpc-cli-deactivate-mtls-when-no-certificates-and-no-key-provided-upon-start~1] None => { - let mut client = - CliConnectionClient::connect(self.server_address.to_string()).await?; + let mut client = match &self.server_endpoint { + ServerEndpoint::Tcp(endpoint) => { + CliConnectionClient::connect(endpoint.to_string()).await? + } + ServerEndpoint::Unix(path) => { + let path = path.clone(); + let channel = Endpoint::try_from("http://[::]:50061") + .map_err(|err| { + GrpcMiddlewareError::ConnectionInterrupted(err.to_string()) + })? + .connect_with_connector(service_fn(move |_| { + let path = path.clone(); + async move { UnixStream::connect(path).await.map(TokioIo::new) } + })) + .await?; + CliConnectionClient::new(channel) + } + }; #[allow(deprecated)] let res = client @@ -319,3 +400,12 @@ impl GRPCCommunicationsClient { } } } + +impl std::fmt::Display for ServerEndpoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ServerEndpoint::Tcp(address) => write!(f, "{address}"), + ServerEndpoint::Unix(path) => write!(f, "unix://{}", path.display()), + } + } +} diff --git a/grpc/tests/grpc_test.rs b/grpc/tests/grpc_test.rs index 4379443ec..5b30c00bb 100644 --- a/grpc/tests/grpc_test.rs +++ b/grpc/tests/grpc_test.rs @@ -14,40 +14,40 @@ #[cfg(test)] mod grpc_tests { - use ankaios_api::ank_base::{ - CompleteState, CompleteStateRequest, Request, RequestContent - }; + use ankaios_api::ank_base::{CompleteState, CompleteStateRequest, Request, RequestContent}; use common::{ commands::{self}, communications_client::CommunicationsClient, communications_error::CommunicationMiddlewareError, - communications_server::CommunicationsServer, + communications_server::{CommunicationsServer, ServerConnection}, from_server_interface::{FromServer, FromServerSender}, to_server_interface::{ToServer, ToServerInterface, ToServerReceiver, ToServerSender}, }; use grpc::{ + CommanderHello, Goodbye, ToServer as ProtoToServer, client::GRPCCommunicationsClient, command_connection_client::CommandConnectionClient, - security::{self, TLSConfig, read_pem_file, PemFileType}, + security::{self, PemFileType, TLSConfig, read_pem_file}, server::GRPCCommunicationsServer, to_server::ToServerEnum, - CommanderHello, Goodbye, ToServer as ProtoToServer, }; use std::{ collections::HashMap, fs::File, io::{self, Write}, - net::SocketAddr, os::unix::fs::PermissionsExt, path::PathBuf, time::Duration, }; + use hyper_util::rt::TokioIo; use tempfile::TempDir; + use tokio::net::UnixStream; use tokio::{sync::mpsc, task::JoinHandle, time::timeout}; use tokio_stream::wrappers::ReceiverStream; - use tonic::transport::Channel; + use tonic::transport::{Channel, Endpoint}; + use tower::service_fn; /* 10 years validity issued at 08/16/2024 check validity if tests are failing */ static TEST_CA_PEM_CONTENT: &str = r#"-----BEGIN CERTIFICATE----- @@ -183,22 +183,27 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb pub fn get_server_tls_config(&self) -> TLSConfig { TLSConfig { ca_pem: read_pem_file(&self.ca_pem_file_path, PemFileType::Certificate).unwrap(), - crt_pem: read_pem_file(&self.server_pem_file_path, PemFileType::Certificate).unwrap(), - key_pem: read_pem_file(&self.server_key_pem_file_path, PemFileType::PrivateKey).unwrap(), + crt_pem: read_pem_file(&self.server_pem_file_path, PemFileType::Certificate) + .unwrap(), + key_pem: read_pem_file(&self.server_key_pem_file_path, PemFileType::PrivateKey) + .unwrap(), } } pub fn get_agent_tls_config(&self) -> TLSConfig { TLSConfig { ca_pem: read_pem_file(&self.ca_pem_file_path, PemFileType::Certificate).unwrap(), - crt_pem: read_pem_file(&self.agent_pem_file_path, PemFileType::Certificate).unwrap(), - key_pem: read_pem_file(&self.agent_key_pem_file_path, PemFileType::PrivateKey).unwrap(), + crt_pem: read_pem_file(&self.agent_pem_file_path, PemFileType::Certificate) + .unwrap(), + key_pem: read_pem_file(&self.agent_key_pem_file_path, PemFileType::PrivateKey) + .unwrap(), } } pub fn get_cli_tls_config(&self) -> TLSConfig { TLSConfig { ca_pem: read_pem_file(&self.ca_pem_file_path, PemFileType::Certificate).unwrap(), crt_pem: read_pem_file(&self.cli_pem_file_path, PemFileType::Certificate).unwrap(), - key_pem: read_pem_file(&self.cli_key_pem_file_path, PemFileType::PrivateKey).unwrap(), + key_pem: read_pem_file(&self.cli_key_pem_file_path, PemFileType::PrivateKey) + .unwrap(), } } } @@ -281,7 +286,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb // create communication server let mut communications_server = GRPCCommunicationsServer::new(to_server, server_tls_config); - let socket_addr: SocketAddr = server_addr.parse().unwrap(); + let socket_addr = ServerConnection::Tcp(server_addr.parse().unwrap()); let grpc_server_task = tokio::spawn(async move { communications_server @@ -505,7 +510,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb let (to_server, server_receiver) = mpsc::channel::(20); let mut communications_server = GRPCCommunicationsServer::new(to_server, None); - let socket_addr: SocketAddr = server_addr.parse().unwrap(); + let socket_addr = ServerConnection::Tcp(server_addr.parse().unwrap()); let grpc_server_task = tokio::spawn(async move { communications_server @@ -528,9 +533,6 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb panic!("Could not connect to the test gRPC server on '{url}'."); } - // [itest->swdd~grpc-server-creates-commander-connection~1] - // [itest->swdd~grpc-server-provides-endpoint-for-commander-connection-handling~1] - // [itest->swdd~grpc-client-connects-with-unique-commander-connection-name~1] // [itest->swdd~grpc-commander-connection-creates-from-server-channel~1] // [itest->swdd~grpc-commander-connection-checks-version-compatibility~1] // [itest->swdd~grpc-commander-connection-stores-from-server-channel-tx~1] @@ -553,7 +555,8 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb .await .unwrap(); - let mut client = connect_command_client_with_retry("http://127.0.0.1:50055".to_owned()).await; + let mut client = + connect_command_client_with_retry("http://127.0.0.1:50055".to_owned()).await; let _response_stream = client .connect_command(ReceiverStream::new(grpc_client_receiver)) .await @@ -608,7 +611,8 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb .await .unwrap(); - let mut client = connect_command_client_with_retry("http://127.0.0.1:50056".to_owned()).await; + let mut client = + connect_command_client_with_retry("http://127.0.0.1:50056".to_owned()).await; let result = client .connect_command(ReceiverStream::new(grpc_client_receiver)) .await; @@ -631,11 +635,105 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb .await .unwrap(); - let mut client = connect_command_client_with_retry("http://127.0.0.1:50057".to_owned()).await; + let mut client = + connect_command_client_with_retry("http://127.0.0.1:50057".to_owned()).await; let result = client .connect_command(ReceiverStream::new(grpc_client_receiver)) .await; assert_eq!(result.unwrap_err().code(), tonic::Code::InvalidArgument); } + + async fn connect_command_client_with_retry_unix_socket( + socket_path: PathBuf, + ) -> CommandConnectionClient { + for _ in 0..50 { + let path = socket_path.clone(); + let connect_result = Endpoint::try_from("http://[::]:50061") + .unwrap() + .connect_with_connector(service_fn(move |_| { + let path = path.clone(); + async move { UnixStream::connect(path).await.map(TokioIo::new) } + })) + .await; + + if let Ok(channel) = connect_result { + return CommandConnectionClient::new(channel); + } + + tokio::time::sleep(Duration::from_millis(50)).await; + } + + panic!( + "Could not connect to the test gRPC unix socket server on '{}'.", + socket_path.display() + ); + } + + // [itest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [itest->swdd~server-supports-unix-domain-socket-endpoints~1] + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + async fn itest_grpc_communication_command_connection_over_unix_domain_socket() { + let test_request_id = "test_request_id"; + let tempdir = TempDir::new().unwrap(); + let socket_path = tempdir.path().join("ankaios-grpc.sock"); + + let (to_grpc_server, grpc_server_receiver) = mpsc::channel::(20); + let (to_server, mut server_receiver) = mpsc::channel::(20); + + let mut communications_server = GRPCCommunicationsServer::new(to_server, None); + + let socket_endpoint = ServerConnection::Unix(socket_path.clone()); + let _grpc_server_task = tokio::spawn(async move { + communications_server + .start(grpc_server_receiver, socket_endpoint) + .await + }); + + let (client_to_server_tx, grpc_client_receiver) = mpsc::channel::(20); + + client_to_server_tx + .send(ProtoToServer { + to_server_enum: Some(ToServerEnum::CommanderHello(CommanderHello::new())), + }) + .await + .unwrap(); + + let mut client = connect_command_client_with_retry_unix_socket(socket_path).await; + let _response_stream = client + .connect_command(ReceiverStream::new(grpc_client_receiver)) + .await + .unwrap() + .into_inner(); + + client_to_server_tx + .send(ProtoToServer { + to_server_enum: Some(ToServerEnum::Request(Request { + request_id: test_request_id.to_owned(), + request_content: Some(RequestContent::CompleteStateRequest( + CompleteStateRequest { + field_mask: vec![], + subscribe_for_events: false, + }, + )), + })), + }) + .await + .unwrap(); + + let result = timeout(Duration::from_secs(10), server_receiver.recv()).await; + + drop(to_grpc_server); + + assert!(matches!( + result, + Ok(Some(ToServer::Request(Request { + request_id, + request_content: Some(RequestContent::CompleteStateRequest(CompleteStateRequest { + field_mask, + subscribe_for_events, + })), + }))) if request_id.contains(test_request_id) && field_mask.is_empty() && !subscribe_for_events + )); + } } From 91670e3e6600ffde66ce85b5041120fbdf60c07b Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:17:44 +0200 Subject: [PATCH 11/55] Support unix domain sockets in the ank CLI --- ank/config/ank.conf | 7 +-- ank/src/ank_config.rs | 123 +++++++++++++++++++++++++++++++++++++----- ank/src/cli.rs | 7 +-- ank/src/main.rs | 50 +++++++++-------- 4 files changed, 144 insertions(+), 43 deletions(-) diff --git a/ank/config/ank.conf b/ank/config/ank.conf index ec9251310..a479f3809 100644 --- a/ank/config/ank.conf +++ b/ank/config/ank.conf @@ -17,16 +17,17 @@ version = 'v1' # no_wait = false [default] -# The URL to Ankaios server. +# The endpoint to Ankaios server. # If started in insecure mode then the HTTP protocol shall be used, # otherwise the HTTPS protocol shall be used. -# server_url = 'https://127.0.0.1:25551' +address = 'unix:///run/ankaios/server.sock' # The flag to disable TLS communication with the server. # If set to 'true', then the CLI shall not use TLS. -insecure = true +# This option must not be used with unix:// addresses. # The path to the ca certificate pem file. +# TLS certificate options are only valid with TCP addresses. # ca_pem = '/etc/ankaios/certs/ca.pem' # The path to CLI certificate pem file. diff --git a/ank/src/ank_config.rs b/ank/src/ank_config.rs index bdecf525b..fa69e386b 100644 --- a/ank/src/ank_config.rs +++ b/ank/src/ank_config.rs @@ -34,6 +34,7 @@ use std::collections::BTreeMap; use std::env; use std::fmt; use std::fs::read_to_string; +use std::path::Path; use std::path::PathBuf; pub const DEFAULT_CONFIG: &str = "default"; @@ -66,6 +67,30 @@ fn get_default_url() -> String { DEFAULT_SERVER_ADDRESS.to_string() } +fn is_unix_address(address: &str) -> bool { + address.starts_with("unix://") +} + +fn validate_address_format(address: &str) -> Result<(), String> { + if let Some(path) = address.strip_prefix("unix://") { + if path.is_empty() { + return Err("Wrong server address format: empty unix socket path.".to_string()); + } + if !Path::new(path).is_absolute() { + return Err(format!( + "Wrong server address format: unix socket path must be absolute: '{address}'." + )); + } + return Ok(()); + } + + if address.starts_with("https://") || address.starts_with("http://") { + Ok(()) + } else { + Err(format!("Wrong server address format: '{address}'.")) + } +} + // [impl->swdd~cli-loads-config-file~2] #[derive(Debug, PartialEq)] pub struct AnkConfig { @@ -74,7 +99,7 @@ pub struct AnkConfig { pub verbose: bool, pub quiet: bool, pub no_wait: bool, - pub server_url: String, + pub address: String, pub insecure: bool, ca_pem: Option, crt_pem: Option, @@ -95,8 +120,8 @@ struct AnkConfigHelper { quiet: bool, #[serde(default)] no_wait: bool, - #[serde(default = "get_default_url")] - server_url: String, + #[serde(default = "get_default_url", alias = "server_url")] + address: String, #[serde(default)] insecure: bool, ca_pem: Option, @@ -115,7 +140,7 @@ impl From for AnkConfig { verbose: helper.verbose, quiet: helper.quiet, no_wait: helper.no_wait, - server_url: helper.server_url, + address: helper.address, insecure: helper.insecure, ca_pem: helper.ca_pem, crt_pem: helper.crt_pem, @@ -188,7 +213,7 @@ impl Default for AnkConfig { verbose: bool::default(), quiet: bool::default(), no_wait: bool::default(), - server_url: get_default_url(), + address: get_default_url(), insecure: bool::default(), ca_pem: None, crt_pem: None, @@ -237,6 +262,12 @@ impl ConfigFile for AnkConfig { ank_config.key_pem_content = Some(key_pem_content); } + validate_address_format(&ank_config.address) + .map_err(ConversionErrors::InvalidConfig)?; + + validate_socket_configuration(&ank_config) + .map_err(ConversionErrors::InvalidConfig)?; + Ok(ank_config) } } @@ -260,8 +291,8 @@ impl AnkConfig { self.insecure = insecure; } - if let Some(server_url) = &args.server_url { - self.server_url = server_url.to_owned(); + if let Some(address) = &args.address { + self.address = address.to_owned(); } if let Some(ca_pem_path) = &args.ca_pem { @@ -282,10 +313,39 @@ impl AnkConfig { .map_err(|err| err.to_string())?; self.key_pem_content = Some(key_pem_content); } + + validate_address_format(&self.address)?; + validate_socket_configuration(self)?; Ok(()) } } +fn validate_socket_configuration(ank_config: &AnkConfig) -> Result<(), String> { + if is_unix_address(&ank_config.address) { + if ank_config.insecure { + return Err( + "Invalid ank config: 'insecure' must not be enabled for unix:// endpoints" + .to_string(), + ); + } + + if ank_config.ca_pem.is_some() + || ank_config.crt_pem.is_some() + || ank_config.key_pem.is_some() + || ank_config.ca_pem_content.is_some() + || ank_config.crt_pem_content.is_some() + || ank_config.key_pem_content.is_some() + { + return Err( + "Invalid ank config: TLS certificate settings are not allowed for unix:// endpoints" + .to_string(), + ); + } + } + + Ok(()) +} + ////////////////////////////////////////////////////////////////////////////// // ######## ####### ######### ######### // // ## ## ## ## // @@ -413,7 +473,7 @@ mod tests { object_field_mask: Vec::new(), }), }), - server_url: Some(TEST_SERVER_URL.to_string()), + address: Some(TEST_SERVER_URL.to_string()), config_path: Some(super::get_config_file_paths()[0].clone()), response_timeout_ms: Some(5000), insecure: Some(false), @@ -432,7 +492,7 @@ mod tests { assert!(ank_config.quiet); assert!(ank_config.no_wait); assert!(!ank_config.insecure); - assert_eq!(ank_config.server_url, TEST_SERVER_URL.to_string()); + assert_eq!(ank_config.address, TEST_SERVER_URL.to_string()); assert_eq!(ank_config.ca_pem, Some(fixtures::CA_PEM_PATH.to_string())); assert_eq!(ank_config.crt_pem, Some(fixtures::CRT_PEM_PATH.to_string())); assert_eq!(ank_config.key_pem, Some(fixtures::KEY_PEM_PATH.to_string())); @@ -477,7 +537,7 @@ mod tests { object_field_mask: Vec::new(), }), }), - server_url: Some(DEFAULT_SERVER_ADDRESS.to_string()), + address: Some(DEFAULT_SERVER_ADDRESS.to_string()), config_path: Some(super::get_config_file_paths()[0].clone()), response_timeout_ms: Some(5000), insecure: Some(false), @@ -532,7 +592,7 @@ mod tests { object_field_mask: Vec::new(), }), }), - server_url: Some(DEFAULT_SERVER_ADDRESS.to_string()), + address: Some(DEFAULT_SERVER_ADDRESS.to_string()), config_path: Some(super::get_config_file_paths()[0].clone()), response_timeout_ms: Some(5000), insecure: None, @@ -563,7 +623,7 @@ mod tests { let ank_config = AnkConfig::from_file(PathBuf::from(tmp_config_file.path())).unwrap(); - assert_eq!(ank_config.server_url, get_default_url()); + assert_eq!(ank_config.address, get_default_url()); assert!(!ank_config.insecure); assert!(ank_config.ca_pem.is_none()); assert!(ank_config.crt_pem.is_none()); @@ -600,7 +660,7 @@ mod tests { quiet = false no_wait = false [default] - server_url = 'https://127.0.0.1:25551' + address = 'https://127.0.0.1:25551' insecure = false ca_pem_content = '''{}''' crt_pem_content = '''{}''' @@ -624,7 +684,7 @@ mod tests { assert!(!ank_config.verbose); assert!(!ank_config.quiet); assert!(!ank_config.no_wait); - assert_eq!(ank_config.server_url, DEFAULT_SERVER_ADDRESS.to_string()); + assert_eq!(ank_config.address, DEFAULT_SERVER_ADDRESS.to_string()); assert!(!ank_config.insecure); assert_eq!( ank_config.ca_pem_content, @@ -695,4 +755,39 @@ mod tests { unsafe { std::env::set_var("HOME", home) }; } } + + #[test] + fn utest_ank_config_rejects_insecure_with_unix_domain_socket() { + let ank_config_content = r"# + version = 'v1' + [default] + address = 'unix:///tmp/ankaios-cli.sock' + insecure = true + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{ank_config_content}").unwrap(); + + let result = AnkConfig::from_file(PathBuf::from(tmp_config_file.path())); + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } + + #[test] + fn utest_ank_config_rejects_tls_with_unix_domain_socket() { + let ank_config_content = format!( + r"# + version = 'v1' + [default] + address = 'unix:///tmp/ankaios-cli.sock' + ca_pem_content = '''{}''' + #", + fixtures::CA_PEM_CONTENT, + ); + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{ank_config_content}").unwrap(); + + let result = AnkConfig::from_file(PathBuf::from(tmp_config_file.path())); + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } } diff --git a/ank/src/cli.rs b/ank/src/cli.rs index efae64b7a..cb5ad4b09 100644 --- a/ank/src/cli.rs +++ b/ank/src/cli.rs @@ -191,9 +191,10 @@ pub struct AnkCli { /// The path to the server config file. /// The default paths are $HOME/.config/ankaios/ank.conf and /etc/ankaios/ank.conf with first existing file taking precedence over the other. pub config_path: Option, - #[arg(short = 's', long = "server-url", required=false, env = ANK_SERVER_URL_ENV_KEY)] - /// The url to Ankaios server. - pub server_url: Option, + #[arg(short = 's', long = "address", alias = "server-url", required=false, env = ANK_SERVER_URL_ENV_KEY)] + /// The endpoint to Ankaios server. + /// Supported values are https://host:port and unix:///path/to/socket. + pub address: Option, #[arg( long = "response-timeout", required = false, diff --git a/ank/src/main.rs b/ank/src/main.rs index 0e07bdb0b..17b22afbc 100644 --- a/ank/src/main.rs +++ b/ank/src/main.rs @@ -53,35 +53,39 @@ async fn main() { ank_config ); - if let Err(err_message) = TLSConfig::is_config_conflicting( - ank_config.insecure, - &ank_config.ca_pem_content, - &ank_config.crt_pem_content, - &ank_config.key_pem_content, - ) { - output_warn!("{}", err_message); - } + let tls_config = if ank_config.address.starts_with("unix://") { + None + } else { + if let Err(err_message) = TLSConfig::is_config_conflicting( + ank_config.insecure, + &ank_config.ca_pem_content, + &ank_config.crt_pem_content, + &ank_config.key_pem_content, + ) { + output_warn!("{}", err_message); + } - // [impl->swdd~cli-provides-file-paths-to-communication-middleware~1] - // [impl->swdd~cli-establishes-insecure-communication-based-on-provided-insecure-cli-argument~1] - // [impl->swdd~cli-fails-on-missing-file-paths-and-insecure-cli-arguments~1] - let tls_config = TLSConfig::new( - ank_config.insecure, - ank_config.ca_pem_content.clone(), - ank_config.crt_pem_content.clone(), - ank_config.key_pem_content.clone(), - ); + // [impl->swdd~cli-provides-file-paths-to-communication-middleware~1] + // [impl->swdd~cli-establishes-insecure-communication-based-on-provided-insecure-cli-argument~1] + // [impl->swdd~cli-fails-on-missing-file-paths-and-insecure-cli-arguments~1] + TLSConfig::new( + ank_config.insecure, + ank_config.ca_pem_content.clone(), + ank_config.crt_pem_content.clone(), + ank_config.key_pem_content.clone(), + ) + .unwrap_or_exit_func( + |err| output_and_error!("Missing certificate files: {}", err), + -1, + ) + }; let mut cmd = CliCommands::init( ank_config.response_timeout, cli_name.to_string(), - ank_config.server_url.clone(), + ank_config.address.clone(), ank_config.no_wait, - // [impl->swdd~cli-fails-on-missing-file-paths-and-insecure-cli-arguments~1] - tls_config.unwrap_or_exit_func( - |err| output_and_error!("Missing certificate files: {}", err), - -1, - ), + tls_config, ) .unwrap_or_else(|err| { output_and_error!("Cannot connect to server: '{}'", err); From 6a982ced0fff2fd9b75daf347f16e44a882a412e Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:25:42 +0200 Subject: [PATCH 12/55] Use domain sockets in devcontainer --- .devcontainer/Dockerfile | 8 ++++---- tools/dev_scripts/ankaios-secure-start | 6 +++++- tools/dev_scripts/ankaios-start | 18 ++++++++++++++---- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9c70eb5cc..a47c5af06 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -34,10 +34,10 @@ RUN echo 'alias ankaios-start="/workspaces/ankaios/tools/dev_scripts/ankaios-sta RUN echo 'alias ankaios-clean="/workspaces/ankaios/tools/dev_scripts/ankaios-clean"' | tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc RUN echo 'export PATH=$PATH:/workspaces/ankaios/target/x86_64-unknown-linux-musl/debug' | tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc -# work in insecure mode in the dev container -RUN echo 'export ANKAGENT_INSECURE=true' | tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc -RUN echo 'export ANKSERVER_INSECURE=true' | tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc -RUN echo 'export ANK_INSECURE=true' | tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc +# use unix domain socket defaults in the dev container +RUN echo 'export ANK_SERVER_URL=unix:///tmp/ankaios.sock' | tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc +RUN echo 'export ANKSERVER_SERVER_URL=unix:///tmp/ankaios.sock' | tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc +RUN echo 'export ANKAGENT_SERVER_URL=unix:///tmp/ankaios.sock' | tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc # add some convinence aliases RUN echo 'alias ll="ls -la"' | tee -a /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc diff --git a/tools/dev_scripts/ankaios-secure-start b/tools/dev_scripts/ankaios-secure-start index e848752ab..8b694c15d 100755 --- a/tools/dev_scripts/ankaios-secure-start +++ b/tools/dev_scripts/ankaios-secure-start @@ -5,18 +5,22 @@ SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) LOG_DIR="$(readlink -f "$SCRIPT_DIR/../../logs")" CERTS_FOLDER_NAME=".certs" CERTS_DIR="$SCRIPT_DIR/../../$CERTS_FOLDER_NAME" +DEFAULT_TCP_SERVER_ADDRESS="127.0.0.1:25551" +DEFAULT_TCP_CLI_ADDRESS="https://127.0.0.1:25551" mkdir -p "$LOG_DIR" "$SCRIPT_DIR/../certs/create_certs.sh" echo "Staring up Ankaios server with TLS enabled" +ANKSERVER_SERVER_URL="$DEFAULT_TCP_SERVER_ADDRESS" \ ANKSERVER_CA_PEM="$CERTS_DIR/ca.pem" \ ANKSERVER_CRT_PEM="$CERTS_DIR/server.pem" \ ANKSERVER_KEY_PEM="$CERTS_DIR/server-key.pem" \ RUST_LOG=debug /workspaces/ankaios/target/x86_64-unknown-linux-musl/debug/ank-server 2> "$LOG_DIR/ank-server" & echo "Staring up Ankaios agent with TLS enabled" +ANKAGENT_SERVER_URL="$DEFAULT_TCP_CLI_ADDRESS" \ ANKAGENT_CA_PEM="$CERTS_DIR/ca.pem" \ ANKAGENT_CRT_PEM="$CERTS_DIR/agent.pem" \ ANKAGENT_KEY_PEM="$CERTS_DIR/agent-key.pem" \ @@ -26,7 +30,7 @@ RED='\033[0;31m' NC='\033[0m' # No Color echo -e "${RED}To use certificates for the ank cli run the cli as follows:${NC}" -echo 'ank --ca_pem /workspaces/ankaios/.certs/ca.pem --crt_pem /workspaces/ankaios/.certs/cli.pem --key_pem /workspaces/ankaios/.certs/cli-key.pem get workloads' +echo 'ank --address "'"$DEFAULT_TCP_CLI_ADDRESS"'" --ca_pem /workspaces/ankaios/.certs/ca.pem --crt_pem /workspaces/ankaios/.certs/cli.pem --key_pem /workspaces/ankaios/.certs/cli-key.pem get workloads' echo '' diff --git a/tools/dev_scripts/ankaios-start b/tools/dev_scripts/ankaios-start index 4925788d6..5a8ca99f8 100755 --- a/tools/dev_scripts/ankaios-start +++ b/tools/dev_scripts/ankaios-start @@ -4,6 +4,7 @@ set -e DEFAULT_BIN_DIR="/workspaces/ankaios/target/x86_64-unknown-linux-musl/debug" SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" LOG_DIR="$(readlink -f "$SCRIPT_DIR/../../logs")" +DEFAULT_UDS_ADDRESS="unix:///tmp/ankaios.sock" "$SCRIPT_DIR/ankaios-clean" @@ -14,8 +15,17 @@ fi mkdir -p "$LOG_DIR" -echo "Staring up Ankaios server with TLS disabled" -RUST_LOG=debug "$DEFAULT_BIN_DIR/ank-server" --insecure 2> "$LOG_DIR/ank-server" & +export ANKSERVER_SERVER_URL="${ANKSERVER_SERVER_URL:-$DEFAULT_UDS_ADDRESS}" +export ANKAGENT_SERVER_URL="${ANKAGENT_SERVER_URL:-$DEFAULT_UDS_ADDRESS}" +export ANK_SERVER_URL="${ANK_SERVER_URL:-$DEFAULT_UDS_ADDRESS}" -echo "Staring up Ankaios agent with TLS disabled" -RUST_LOG=debug "$DEFAULT_BIN_DIR/ank-agent" --insecure --name agent_A 2> "$LOG_DIR/ank-agent" & +if [[ "$ANKSERVER_SERVER_URL" == unix://* ]]; then + SOCKET_PATH="${ANKSERVER_SERVER_URL#unix://}" + rm -f "$SOCKET_PATH" +fi + +echo "Staring up Ankaios server using address '$ANKSERVER_SERVER_URL'" +RUST_LOG=debug "$DEFAULT_BIN_DIR/ank-server" 2> "$LOG_DIR/ank-server" & + +echo "Staring up Ankaios agent using address '$ANKAGENT_SERVER_URL'" +RUST_LOG=debug "$DEFAULT_BIN_DIR/ank-agent" --name agent_A 2> "$LOG_DIR/ank-agent" & From 0d3faa34641b3aef1f33ecae181b22a59e1e5304 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:50:43 +0200 Subject: [PATCH 13/55] Update install script to use unix domain socket --- tools/install.sh | 60 ++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/tools/install.sh b/tools/install.sh index 71f0235e2..8fe41869f 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -11,6 +11,8 @@ HOME_CONFIG_DEST="${HOME}/.config/ankaios/" FILE_STARTUP_STATE="${CONFIG_DEST}/state.yaml" INSTALL_TYPE="both" SERVICE_DEST=/etc/systemd/system +ANK_SERVER_RUNTIME_DIR="/run/ankaios" +ANK_SYSTEM_GROUP="ankaios" ANK_SERVER_SERVICE="ank-server" FILE_ANK_SERVER_SERVICE="${SERVICE_DEST}/${ANK_SERVER_SERVICE}.service" @@ -64,6 +66,18 @@ download_release() { fi } +create_ankaios_system_group() { + local group_mgmt_sudo="sudo" + if [ -w "/etc" ]; then + group_mgmt_sudo="" + fi + + if ! getent group "${ANK_SYSTEM_GROUP}" >/dev/null; then + echo "Creating system group '${ANK_SYSTEM_GROUP}'" + ${group_mgmt_sudo} groupadd --system "${ANK_SYSTEM_GROUP}" + fi +} + cleanup_routine() { if [ -d "${ANKAIOS_TMP_DIR}" ]; then rm -rf "${ANKAIOS_TMP_DIR}" @@ -146,9 +160,18 @@ if [ -w "${BIN_DESTINATION}" ]; then BIN_SUDO="" fi +RUNTIME_SUDO="sudo" +if [ -w "/run" ]; then + RUNTIME_SUDO="" +fi + echo "Extracting the binaries into install folder: '${BIN_DESTINATION}'" ${BIN_SUDO} tar --no-same-owner -m -xvzf "${RELEASE_FILE_NAME}" -C "${BIN_DESTINATION}/" +if [[ "$INSTALL_TYPE" == server || "$INSTALL_TYPE" == both ]]; then + create_ankaios_system_group +fi + # Install systemd unit files if [ -d "$SERVICE_DEST" ]; then SVC_SUDO="sudo" @@ -165,6 +188,9 @@ Wants=network.target [Service] Environment="RUST_LOG=${INSTALL_ANK_SERVER_RUST_LOG}" +ExecStartPre=/usr/bin/mkdir -p /run/ankaios +ExecStartPre=/usr/bin/chgrp ${ANK_SYSTEM_GROUP} /run/ankaios +ExecStartPre=/usr/bin/chmod 0750 /run/ankaios ExecStart=${BIN_DESTINATION}/ank-server [Install] @@ -194,6 +220,13 @@ else echo "$$SERVICE_DEST not found. Skipping installation of systemd unit files for Ankaios" fi +if [[ "$INSTALL_TYPE" == server || "$INSTALL_TYPE" == both ]]; then + echo "Preparing server runtime directory: '${ANK_SERVER_RUNTIME_DIR}'" + $RUNTIME_SUDO mkdir -p "${ANK_SERVER_RUNTIME_DIR}" + $RUNTIME_SUDO chown "root:${ANK_SYSTEM_GROUP}" "${ANK_SERVER_RUNTIME_DIR}" + $RUNTIME_SUDO chmod 0750 "${ANK_SERVER_RUNTIME_DIR}" +fi + # Write sample state startup config if [[ "$INSTALL_TYPE" == server || "$INSTALL_TYPE" == both ]]; then if ! [ -s "$FILE_STARTUP_STATE" ]; then @@ -302,31 +335,4 @@ ${BIN_SUDO} chmod +x "${BIN_DESTINATION}/${BASEFILE_ANK_UNINSTALL}" echo "Created uninstall script ${BIN_DESTINATION}/${BASEFILE_ANK_UNINSTALL}." -if [ -t 1 ]; then - WARN_COLOR="$(printf '\033[1;33m')" - RESET_COLOR="$(printf '\033[0m')" -else - WARN_COLOR="" - RESET_COLOR="" -fi - -cat << EOF - -${WARN_COLOR}!!!!!!!!!!!!!!!!!!!!!!!!!!!! SECURITY WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - The install script does NOT enable any authentication for access to the - Ankaios server. - - Since the server is typically run as root (e.g. via "sudo systemctl"), any - client that can reach it could start workloads with root privileges. - - To secure your setup, see "Setting up Ankaios with mTLS" in the docs: - https://eclipse-ankaios.github.io/ankaios/latest/usage/mtls-setup/ - - ==> Only use this setup for development purposes. <== - -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!${RESET_COLOR} - -EOF - echo "Installation has finished." From 1951254a6cba18fd96536ef479e72c17f96cdf02 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:53:42 +0200 Subject: [PATCH 14/55] Use unix domain socket in debian package --- tools/debian/build_src_pkg.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/debian/build_src_pkg.sh b/tools/debian/build_src_pkg.sh index 1cf8497a2..5ca8e1380 100755 --- a/tools/debian/build_src_pkg.sh +++ b/tools/debian/build_src_pkg.sh @@ -41,6 +41,7 @@ BASE_DIR="$SCRIPT_DIR/../.." MAINTAINER="Eclipse Ankaios " LOG_LEVEL="info" RELEASE_URL="https://github.com/eclipse-ankaios/ankaios/releases/tag/v$ANKAIOS_VERSION" +ANK_SYSTEM_GROUP="ankaios" write_control() { cat > "$BASE_DIR/debian/control" <<'EOF' @@ -110,6 +111,9 @@ Wants=network.target [Service] Environment="RUST_LOG=${LOG_LEVEL}" +ExecStartPre=/usr/bin/mkdir -p /run/ankaios +ExecStartPre=/usr/bin/chgrp ${ANK_SYSTEM_GROUP} /run/ankaios +ExecStartPre=/usr/bin/chmod 0750 /run/ankaios ExecStart=/usr/bin/ank-server [Install] @@ -132,6 +136,21 @@ EOF } +write_postinst() { + cat > "$BASE_DIR/debian/ank-server.postinst" </dev/null; then + addgroup --system ${ANK_SYSTEM_GROUP} +fi + +exit 0 +EOF + chmod +x "$BASE_DIR/debian/ank-server.postinst" +} + write_copyright() { cat > "$BASE_DIR/debian/copyright" < Date: Tue, 8 Sep 2026 16:08:01 +0200 Subject: [PATCH 15/55] Deduplicate code --- agent/src/agent_config.rs | 78 +++++++-------------------- ank/src/ank_config.rs | 83 ++++++++--------------------- common/src/config.rs | 107 +++++++++++++++++++++++++++++++++++++- grpc/src/client.rs | 106 +++++++++++++++---------------------- 4 files changed, 189 insertions(+), 185 deletions(-) diff --git a/agent/src/agent_config.rs b/agent/src/agent_config.rs index 67ca00ef8..df9f9654d 100644 --- a/agent/src/agent_config.rs +++ b/agent/src/agent_config.rs @@ -15,8 +15,10 @@ use crate::cli::Arguments; use crate::io_utils::default_run_folder_string; -use common::DEFAULT_SERVER_ADDRESS; -use common::config::{CONFIG_VERSION, ConfigFile, ConversionErrors}; +use common::config::{ + CONFIG_VERSION, ConfigFile, ConversionErrors, get_default_server_url, + validate_server_address_format, validate_unix_socket_tls_settings, +}; use common::std_extensions::UnreachableOption; use grpc::security::PemFileType; @@ -29,47 +31,18 @@ use tests::read_pem_file; use serde::Deserialize; use std::collections::HashMap; use std::fs::read_to_string; -use std::path::Path; use std::path::PathBuf; use toml::from_str; pub const DEFAULT_AGENT_CONFIG_FILE_PATH: [&str; 1] = ["/etc/ankaios/ank-agent.conf"]; -pub fn get_default_url() -> String { - DEFAULT_SERVER_ADDRESS.to_string() -} - -fn is_unix_address(address: &str) -> bool { - address.starts_with("unix://") -} - -fn validate_address_format(address: &str) -> Result<(), String> { - if let Some(path) = address.strip_prefix("unix://") { - if path.is_empty() { - return Err("Wrong server address format: empty unix socket path.".to_string()); - } - if !Path::new(path).is_absolute() { - return Err(format!( - "Wrong server address format: unix socket path must be absolute: '{address}'." - )); - } - return Ok(()); - } - - if address.starts_with("https://") || address.starts_with("http://") { - Ok(()) - } else { - Err(format!("Wrong server address format: '{address}'.")) - } -} - // [impl->swdd~agent-loads-config-file~2] #[derive(Debug, Deserialize, PartialEq)] pub struct AgentConfig { pub version: String, #[serde(default)] pub name: String, - #[serde(default = "get_default_url", alias = "server_url")] + #[serde(default = "get_default_server_url", alias = "server_url")] pub address: String, #[serde(default = "crate::io_utils::default_run_folder_string")] pub run_folder: String, @@ -92,7 +65,7 @@ impl Default for AgentConfig { AgentConfig { version: CONFIG_VERSION.to_string(), name: String::new(), - address: get_default_url(), + address: get_default_server_url(), run_folder: default_run_folder_string(), insecure: bool::default(), runtimes: None, @@ -144,7 +117,8 @@ impl ConfigFile for AgentConfig { agent_config.key_pem_content = Some(key_pem_content); } - validate_address_format(&agent_config.address).map_err(ConversionErrors::InvalidConfig)?; + validate_server_address_format(&agent_config.address) + .map_err(ConversionErrors::InvalidConfig)?; validate_socket_configuration(&agent_config).map_err(ConversionErrors::InvalidConfig)?; @@ -193,7 +167,7 @@ impl AgentConfig { self.tags = tags.iter().cloned().collect(); } - validate_address_format(&self.address)?; + validate_server_address_format(&self.address)?; validate_socket_configuration(self)?; Ok(()) @@ -201,30 +175,17 @@ impl AgentConfig { } fn validate_socket_configuration(agent_config: &AgentConfig) -> Result<(), String> { - // keep behavior aligned with server validation for unix:// endpoints - if is_unix_address(&agent_config.address) { - if agent_config.insecure { - return Err( - "Invalid agent config: 'insecure' must not be enabled for unix:// endpoints" - .to_string(), - ); - } - - if agent_config.ca_pem.is_some() + validate_unix_socket_tls_settings( + "agent", + &agent_config.address, + agent_config.insecure, + agent_config.ca_pem.is_some() || agent_config.crt_pem.is_some() || agent_config.key_pem.is_some() || agent_config.ca_pem_content.is_some() || agent_config.crt_pem_content.is_some() - || agent_config.key_pem_content.is_some() - { - return Err( - "Invalid agent config: TLS certificate settings are not allowed for unix:// endpoints" - .to_string(), - ); - } - } - - Ok(()) + || agent_config.key_pem_content.is_some(), + ) } ////////////////////////////////////////////////////////////////////////////// @@ -243,7 +204,7 @@ mod tests { use common::config::{ConfigFile, ConversionErrors}; use ankaios_api::test_utils::fixtures; - use common::DEFAULT_SERVER_ADDRESS; + use common::{DEFAULT_SERVER_ADDRESS, config::get_default_server_url}; use std::io::Write; use std::path::PathBuf; @@ -278,10 +239,7 @@ mod tests { fn utest_default_agent_config() { let default_agent_config = AgentConfig::default(); - assert_eq!( - default_agent_config.address, - DEFAULT_SERVER_ADDRESS.to_string() - ); + assert_eq!(default_agent_config.address, get_default_server_url()); assert!(!default_agent_config.insecure); assert_eq!(default_agent_config.version, CONFIG_VERSION); } diff --git a/ank/src/ank_config.rs b/ank/src/ank_config.rs index fa69e386b..013726151 100644 --- a/ank/src/ank_config.rs +++ b/ank/src/ank_config.rs @@ -14,8 +14,10 @@ use crate::cli::AnkCli; use crate::output_warn; -use common::DEFAULT_SERVER_ADDRESS; -use common::config::{CONFIG_VERSION, ConfigFile, ConversionErrors}; +use common::config::{ + CONFIG_VERSION, ConfigFile, ConversionErrors, get_default_server_url, + validate_server_address_format, validate_unix_socket_tls_settings, +}; use common::std_extensions::UnreachableOption; use grpc::security::PemFileType; @@ -34,7 +36,6 @@ use std::collections::BTreeMap; use std::env; use std::fmt; use std::fs::read_to_string; -use std::path::Path; use std::path::PathBuf; pub const DEFAULT_CONFIG: &str = "default"; @@ -49,7 +50,10 @@ fn get_user_config_path() -> Option { pub fn get_config_file_paths() -> Vec { if let Some(user_config_path) = get_user_config_path() { - vec![user_config_path, DEFAULT_ANK_CONFIG_SYSTEM_FILE_PATH.to_string()] + vec![ + user_config_path, + DEFAULT_ANK_CONFIG_SYSTEM_FILE_PATH.to_string(), + ] } else { output_warn!( "HOME environment variable not found, continue with searching only system config file at '{}'.", @@ -63,34 +67,6 @@ fn get_default_response_timeout() -> u64 { DEFAULT_RESPONSE_TIMEOUT } -fn get_default_url() -> String { - DEFAULT_SERVER_ADDRESS.to_string() -} - -fn is_unix_address(address: &str) -> bool { - address.starts_with("unix://") -} - -fn validate_address_format(address: &str) -> Result<(), String> { - if let Some(path) = address.strip_prefix("unix://") { - if path.is_empty() { - return Err("Wrong server address format: empty unix socket path.".to_string()); - } - if !Path::new(path).is_absolute() { - return Err(format!( - "Wrong server address format: unix socket path must be absolute: '{address}'." - )); - } - return Ok(()); - } - - if address.starts_with("https://") || address.starts_with("http://") { - Ok(()) - } else { - Err(format!("Wrong server address format: '{address}'.")) - } -} - // [impl->swdd~cli-loads-config-file~2] #[derive(Debug, PartialEq)] pub struct AnkConfig { @@ -120,7 +96,7 @@ struct AnkConfigHelper { quiet: bool, #[serde(default)] no_wait: bool, - #[serde(default = "get_default_url", alias = "server_url")] + #[serde(default = "get_default_server_url", alias = "server_url")] address: String, #[serde(default)] insecure: bool, @@ -213,7 +189,7 @@ impl Default for AnkConfig { verbose: bool::default(), quiet: bool::default(), no_wait: bool::default(), - address: get_default_url(), + address: get_default_server_url(), insecure: bool::default(), ca_pem: None, crt_pem: None, @@ -262,11 +238,10 @@ impl ConfigFile for AnkConfig { ank_config.key_pem_content = Some(key_pem_content); } - validate_address_format(&ank_config.address) + validate_server_address_format(&ank_config.address) .map_err(ConversionErrors::InvalidConfig)?; - validate_socket_configuration(&ank_config) - .map_err(ConversionErrors::InvalidConfig)?; + validate_socket_configuration(&ank_config).map_err(ConversionErrors::InvalidConfig)?; Ok(ank_config) } @@ -314,36 +289,24 @@ impl AnkConfig { self.key_pem_content = Some(key_pem_content); } - validate_address_format(&self.address)?; + validate_server_address_format(&self.address)?; validate_socket_configuration(self)?; Ok(()) } } fn validate_socket_configuration(ank_config: &AnkConfig) -> Result<(), String> { - if is_unix_address(&ank_config.address) { - if ank_config.insecure { - return Err( - "Invalid ank config: 'insecure' must not be enabled for unix:// endpoints" - .to_string(), - ); - } - - if ank_config.ca_pem.is_some() + validate_unix_socket_tls_settings( + "ank", + &ank_config.address, + ank_config.insecure, + ank_config.ca_pem.is_some() || ank_config.crt_pem.is_some() || ank_config.key_pem.is_some() || ank_config.ca_pem_content.is_some() || ank_config.crt_pem_content.is_some() - || ank_config.key_pem_content.is_some() - { - return Err( - "Invalid ank config: TLS certificate settings are not allowed for unix:// endpoints" - .to_string(), - ); - } - } - - Ok(()) + || ank_config.key_pem_content.is_some(), + ) } ////////////////////////////////////////////////////////////////////////////// @@ -358,13 +321,13 @@ fn validate_socket_configuration(ank_config: &AnkConfig) -> Result<(), String> { mod tests { use super::AnkConfig; use crate::{ - ank_config::{get_default_response_timeout, get_default_url}, + ank_config::get_default_response_timeout, cli::{AnkCli, Commands, GetArgs, GetCommands}, }; use ankaios_api::test_utils::fixtures; - use common::DEFAULT_SERVER_ADDRESS; use common::config::{ConfigFile, ConversionErrors}; + use common::{DEFAULT_SERVER_ADDRESS, config::get_default_server_url}; use std::io::Write; use std::path::PathBuf; @@ -623,7 +586,7 @@ mod tests { let ank_config = AnkConfig::from_file(PathBuf::from(tmp_config_file.path())).unwrap(); - assert_eq!(ank_config.address, get_default_url()); + assert_eq!(ank_config.address, get_default_server_url()); assert!(!ank_config.insecure); assert!(ank_config.ca_pem.is_none()); assert!(ank_config.crt_pem.is_none()); diff --git a/common/src/config.rs b/common/src/config.rs index 29bf1a504..cf9643d62 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -14,8 +14,11 @@ use crate::std_extensions::GracefulExitResult; use std::fmt; +use std::path::Path; use std::path::PathBuf; +use crate::DEFAULT_SERVER_ADDRESS; + // [impl->swdd~common-config-handling~1] pub const CONFIG_VERSION: &str = "v1"; @@ -32,6 +35,53 @@ pub trait ConfigFile: Default + Sized { fn from_file(file_path: PathBuf) -> Result; } +pub fn get_default_server_url() -> String { + DEFAULT_SERVER_ADDRESS.to_string() +} + +pub fn validate_server_address_format(address: &str) -> Result<(), String> { + if let Some(path) = address.strip_prefix("unix://") { + if path.is_empty() { + return Err("Wrong server address format: empty unix socket path.".to_string()); + } + if !Path::new(path).is_absolute() { + return Err(format!( + "Wrong server address format: unix socket path must be absolute: '{address}'." + )); + } + return Ok(()); + } + + if address.starts_with("https://") || address.starts_with("http://") { + Ok(()) + } else { + Err(format!("Wrong server address format: '{address}'.")) + } +} + +pub fn validate_unix_socket_tls_settings( + config_name: &str, + address: &str, + insecure: bool, + has_tls_certificate_settings: bool, +) -> Result<(), String> { + if address.starts_with("unix://") { + if insecure { + return Err(format!( + "Invalid {config_name} config: 'insecure' must not be enabled for unix:// endpoints" + )); + } + + if has_tls_certificate_settings { + return Err(format!( + "Invalid {config_name} config: TLS certificate settings are not allowed for unix:// endpoints" + )); + } + } + + Ok(()) +} + impl fmt::Display for ConversionErrors { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -102,7 +152,11 @@ pub fn handle_config(config_path: &Option, default_paths: // [utest->swdd~common-config-handling~1] #[cfg(test)] mod tests { - use super::{ConfigFile, ConversionErrors, handle_config}; + use super::{ + ConfigFile, ConversionErrors, get_default_server_url, handle_config, + validate_server_address_format, validate_unix_socket_tls_settings, + }; + use crate::DEFAULT_SERVER_ADDRESS; use crate::std_extensions::UnreachableOption; use serde::Deserialize; use std::fs::read_to_string; @@ -137,6 +191,57 @@ mod tests { } } + #[test] + fn utest_get_default_server_url() { + assert_eq!(get_default_server_url(), DEFAULT_SERVER_ADDRESS.to_string()); + } + + #[test] + fn utest_validate_server_address_format_accepts_http_https_and_unix_absolute() { + assert!(validate_server_address_format("http://127.0.0.1:25551").is_ok()); + assert!(validate_server_address_format("https://127.0.0.1:25551").is_ok()); + assert!(validate_server_address_format("unix:///tmp/ank.sock").is_ok()); + } + + #[test] + fn utest_validate_server_address_format_rejects_invalid_address() { + assert!(validate_server_address_format("127.0.0.1:25551").is_err()); + } + + #[test] + fn utest_validate_server_address_format_rejects_empty_unix_path() { + assert_eq!( + validate_server_address_format("unix://"), + Err("Wrong server address format: empty unix socket path.".to_string()) + ); + } + + #[test] + fn utest_validate_server_address_format_rejects_relative_unix_path() { + assert!(validate_server_address_format("unix://tmp/ank.sock").is_err()); + } + + #[test] + fn utest_validate_unix_socket_tls_settings_allows_tcp_with_tls_or_insecure() { + assert!( + validate_unix_socket_tls_settings("ank", "https://127.0.0.1:25551", true, true).is_ok() + ); + } + + #[test] + fn utest_validate_unix_socket_tls_settings_rejects_unix_with_insecure() { + assert!( + validate_unix_socket_tls_settings("ank", "unix:///tmp/ank.sock", true, false).is_err() + ); + } + + #[test] + fn utest_validate_unix_socket_tls_settings_rejects_unix_with_tls() { + assert!( + validate_unix_socket_tls_settings("ank", "unix:///tmp/ank.sock", false, true).is_err() + ); + } + #[derive(Debug, Deserialize, PartialEq)] struct TestConfig { test_string: String, diff --git a/grpc/src/client.rs b/grpc/src/client.rs index f0b3fbd0b..db0566936 100644 --- a/grpc/src/client.rs +++ b/grpc/src/client.rs @@ -100,6 +100,42 @@ fn parse_server_endpoint( ))) } +fn build_client_tls_config(tls_config: &TLSConfig) -> ClientTlsConfig { + // [impl->swdd~grpc-supports-pem-file-format-for-X509-certificates~1] + let ca = Certificate::from_pem(&tls_config.ca_pem); + // [impl->swdd~grpc-supports-pem-file-format-for-X509-certificates~1] + let client_cert = Certificate::from_pem(&tls_config.crt_pem); + + // [impl->swdd~grpc-supports-pem-file-format-for-keys~1] + let client_key = Certificate::from_pem(&tls_config.key_pem); + let client_identity = Identity::from_pem(client_cert, client_key); + + ClientTlsConfig::new() + .domain_name("ank-server") + .ca_certificate(ca) + .identity(client_identity) +} + +fn get_tcp_endpoint_for_tls(server_endpoint: &ServerEndpoint) -> Result<&str, GrpcMiddlewareError> { + match server_endpoint { + ServerEndpoint::Tcp(endpoint) => Ok(endpoint.as_str()), + ServerEndpoint::Unix(_) => Err(GrpcMiddlewareError::ConnectionInterrupted( + "TLS is not supported for unix:// endpoints".to_string(), + )), + } +} + +async fn connect_unix_channel(path: PathBuf) -> Result { + Endpoint::try_from("http://[::]:50061") + .map_err(|err| GrpcMiddlewareError::ConnectionInterrupted(err.to_string()))? + .connect_with_connector(service_fn(move |_| { + let path = path.clone(); + async move { UnixStream::connect(path).await.map(TokioIo::new) } + })) + .await + .map_err(Into::into) +} + impl GRPCCommunicationsClient { pub fn new_agent_communication( name: String, @@ -264,28 +300,8 @@ impl GRPCCommunicationsClient { ConnectionType::Agent => match &self.tls_config { // [impl->swdd~grpc-agent-activate-mtls-when-certificates-and-key-provided-upon-start~1] Some(tls_config) => { - // [impl->swdd~grpc-supports-pem-file-format-for-X509-certificates~1] - let ca = Certificate::from_pem(&tls_config.ca_pem); - // [impl->swdd~grpc-supports-pem-file-format-for-X509-certificates~1] - let client_cert = Certificate::from_pem(&tls_config.crt_pem); - - // [impl->swdd~grpc-supports-pem-file-format-for-keys~1] - let client_key = Certificate::from_pem(&tls_config.key_pem); - let client_identity = Identity::from_pem(client_cert, client_key); - - let tls = ClientTlsConfig::new() - .domain_name("ank-server") - .ca_certificate(ca) - .identity(client_identity); - - let tcp_endpoint = match &self.server_endpoint { - ServerEndpoint::Tcp(endpoint) => endpoint, - ServerEndpoint::Unix(_) => { - return Err(GrpcMiddlewareError::ConnectionInterrupted( - "TLS is not supported for unix:// endpoints".to_string(), - )); - } - }; + let tls = build_client_tls_config(tls_config); + let tcp_endpoint = get_tcp_endpoint_for_tls(&self.server_endpoint)?; let channel = Channel::from_shared(tcp_endpoint.to_string()) .map_err(|err| GrpcMiddlewareError::TLSError(err.to_string()))? @@ -307,16 +323,7 @@ impl GRPCCommunicationsClient { AgentConnectionClient::connect(endpoint.to_string()).await? } ServerEndpoint::Unix(path) => { - let path = path.clone(); - let channel = Endpoint::try_from("http://[::]:50061") - .map_err(|err| { - GrpcMiddlewareError::ConnectionInterrupted(err.to_string()) - })? - .connect_with_connector(service_fn(move |_| { - let path = path.clone(); - async move { UnixStream::connect(path).await.map(TokioIo::new) } - })) - .await?; + let channel = connect_unix_channel(path.clone()).await?; AgentConnectionClient::new(channel) } }; @@ -331,28 +338,8 @@ impl GRPCCommunicationsClient { ConnectionType::Cli => match &self.tls_config { // [impl->swdd~grpc-cli-activate-mtls-when-certificates-and-key-provided-upon-start~1] Some(tls_config) => { - // [impl->swdd~grpc-supports-pem-file-format-for-X509-certificates~1] - let ca = Certificate::from_pem(&tls_config.ca_pem); - // [impl->swdd~grpc-supports-pem-file-format-for-X509-certificates~1] - let client_cert = Certificate::from_pem(&tls_config.crt_pem); - - // [impl->swdd~grpc-supports-pem-file-format-for-keys~1] - let client_key = Certificate::from_pem(&tls_config.key_pem); - let client_identity = Identity::from_pem(client_cert, client_key); - - let tls = ClientTlsConfig::new() - .domain_name("ank-server") - .ca_certificate(ca) - .identity(client_identity); - - let tcp_endpoint = match &self.server_endpoint { - ServerEndpoint::Tcp(endpoint) => endpoint, - ServerEndpoint::Unix(_) => { - return Err(GrpcMiddlewareError::ConnectionInterrupted( - "TLS is not supported for unix:// endpoints".to_string(), - )); - } - }; + let tls = build_client_tls_config(tls_config); + let tcp_endpoint = get_tcp_endpoint_for_tls(&self.server_endpoint)?; let channel = Channel::from_shared(tcp_endpoint.to_string()) .map_err(|err| GrpcMiddlewareError::TLSError(err.to_string()))? @@ -375,16 +362,7 @@ impl GRPCCommunicationsClient { CliConnectionClient::connect(endpoint.to_string()).await? } ServerEndpoint::Unix(path) => { - let path = path.clone(); - let channel = Endpoint::try_from("http://[::]:50061") - .map_err(|err| { - GrpcMiddlewareError::ConnectionInterrupted(err.to_string()) - })? - .connect_with_connector(service_fn(move |_| { - let path = path.clone(); - async move { UnixStream::connect(path).await.map(TokioIo::new) } - })) - .await?; + let channel = connect_unix_channel(path.clone()).await?; CliConnectionClient::new(channel) } }; From 6ee318b00493ec9298306e0f1137f6a179c90b23 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Fri, 11 Sep 2026 12:13:36 +0200 Subject: [PATCH 16/55] Add unit test for unix socket setup --- grpc/src/lib.rs | 55 ++- grpc/src/server.rs | 76 +--- grpc/src/server_unix_listener.rs | 613 +++++++++++++++++++++++++++++++ 3 files changed, 655 insertions(+), 89 deletions(-) create mode 100644 grpc/src/server_unix_listener.rs diff --git a/grpc/src/lib.rs b/grpc/src/lib.rs index 23919b248..ab0441c17 100644 --- a/grpc/src/lib.rs +++ b/grpc/src/lib.rs @@ -138,13 +138,14 @@ pub mod security { } } -mod client_senders_map; pub mod client; +mod client_senders_map; mod from_server_proxy; mod grpc_agent_connection; mod grpc_cli_connection; mod grpc_commander_connection; pub mod server; +mod server_unix_listener; mod to_server_proxy; use ankaios_api::ank_base; @@ -198,7 +199,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions = temp_file.as_file_mut().metadata().unwrap().permissions(); permissions.set_mode(0o644); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -213,7 +216,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o777); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -228,7 +233,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o722); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -243,7 +250,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o610); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -258,7 +267,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o660); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -273,7 +284,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o602); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::PrivateKey) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -363,7 +376,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o666); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::Certificate).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::Certificate) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -378,7 +393,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o620); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::Certificate).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::Certificate) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -393,7 +410,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o602); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::Certificate).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::Certificate) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -408,7 +427,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o755); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::Certificate).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::Certificate) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -423,7 +444,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o744); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::Certificate).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::Certificate) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -438,7 +461,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o645); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::Certificate).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::Certificate) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } @@ -453,7 +478,9 @@ MIIDrzCCAkGgAwIBAgIQBzANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMx permissions.set_mode(0o654); let _ = temp_file.as_file_mut().set_permissions(permissions); - let error = read_pem_file(temp_file.path(), PemFileType::Certificate).err().unwrap(); + let error = read_pem_file(temp_file.path(), PemFileType::Certificate) + .err() + .unwrap(); assert!(matches!(error, GrpcMiddlewareError::CertificateError(_))); } } diff --git a/grpc/src/server.rs b/grpc/src/server.rs index f509d2eca..40f10e626 100644 --- a/grpc/src/server.rs +++ b/grpc/src/server.rs @@ -22,6 +22,7 @@ use crate::grpc_cli_connection::GRPCCliConnection; use crate::grpc_commander_connection::GRPCCommanderConnection; use crate::grpc_middleware_error::GrpcMiddlewareError; use crate::security::TLSConfig; +use crate::server_unix_listener::prepare_unix_listener; use common::communications_error::CommunicationMiddlewareError; use common::communications_server::{CommunicationsServer, ServerConnection}; @@ -29,12 +30,6 @@ use common::from_server_interface::FromServerReceiver; use common::to_server_interface::ToServerSender; use async_trait::async_trait; -use nix::unistd::{Group, chown}; -use std::fs; -use std::os::unix::fs::FileTypeExt; -use std::os::unix::fs::PermissionsExt; -use std::path::Path; -use tokio::net::UnixListener; use tokio_stream::wrappers::UnixListenerStream; use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; @@ -177,75 +172,6 @@ impl CommunicationsServer for GRPCCommunicationsServer { } } -fn prepare_unix_listener( - socket_path: &Path, - socket_group: Option<&str>, -) -> Result { - if socket_path.exists() { - let metadata = fs::metadata(socket_path).map_err(|err| { - CommunicationMiddlewareError(format!( - "Could not access existing unix socket path '{}': {err}", - socket_path.display() - )) - })?; - - if metadata.file_type().is_socket() { - fs::remove_file(socket_path).map_err(|err| { - CommunicationMiddlewareError(format!( - "Could not remove stale unix socket '{}': {err}", - socket_path.display() - )) - })?; - } else { - return Err(CommunicationMiddlewareError(format!( - "Unix socket path '{}' exists and is not a socket file", - socket_path.display() - ))); - } - } - - let listener = UnixListener::bind(socket_path).map_err(|err| { - CommunicationMiddlewareError(format!( - "Could not bind unix socket '{}': {err}", - socket_path.display() - )) - })?; - - if let Some(group_name) = socket_group { - // [impl->swdd~server-configures-unix-domain-socket-group~1] - let group = Group::from_name(group_name) - .map_err(|err| { - CommunicationMiddlewareError(format!( - "Could not resolve unix socket group '{}': {err}", - group_name - )) - })? - .ok_or_else(|| { - CommunicationMiddlewareError(format!( - "Could not resolve unix socket group '{}': group does not exist", - group_name - )) - })?; - - chown(socket_path, None, Some(group.gid)).map_err(|err| { - CommunicationMiddlewareError(format!( - "Could not set unix socket group '{}' on '{}': {err}", - group_name, - socket_path.display() - )) - })?; - - fs::set_permissions(socket_path, fs::Permissions::from_mode(0o660)).map_err(|err| { - CommunicationMiddlewareError(format!( - "Could not set unix socket permissions on '{}': {err}", - socket_path.display() - )) - })?; - } - - Ok(listener) -} - impl GRPCCommunicationsServer { pub fn new(sender: ToServerSender, tls_config: Option) -> Self { GRPCCommunicationsServer { diff --git a/grpc/src/server_unix_listener.rs b/grpc/src/server_unix_listener.rs new file mode 100644 index 000000000..5b17a7cbe --- /dev/null +++ b/grpc/src/server_unix_listener.rs @@ -0,0 +1,613 @@ +// Copyright (c) 2026 Elektrobit Automotive GmbH +// +// This program and the accompanying materials are made available under the +// terms of the Apache License, Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0. +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +use common::communications_error::CommunicationMiddlewareError; + +#[cfg(test)] +use self::tests::shim::{Group, chown, fs}; +#[cfg(not(test))] +use nix::unistd::{Group, chown}; +#[cfg(not(test))] +use std::fs; +use std::os::unix::fs::FileTypeExt; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use tokio::net::UnixListener; + +pub(crate) fn prepare_unix_listener( + socket_path: &Path, + socket_group: Option<&str>, +) -> Result { + prepare_socket_path(socket_path, socket_path.exists())?; + + let listener = UnixListener::bind(socket_path).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not bind unix socket '{}': {err}", + socket_path.display() + )) + })?; + + configure_socket_group(socket_path, socket_group)?; + + Ok(listener) +} + +fn prepare_socket_path( + socket_path: &Path, + path_exists: bool, +) -> Result<(), CommunicationMiddlewareError> { + if !path_exists { + return Ok(()); + } + + let metadata = fs::metadata(socket_path).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not access existing unix socket path '{}': {err}", + socket_path.display() + )) + })?; + + if metadata.file_type().is_socket() { + fs::remove_file(socket_path).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not remove stale unix socket '{}': {err}", + socket_path.display() + )) + })?; + } else { + return Err(CommunicationMiddlewareError(format!( + "Unix socket path '{}' exists and is not a socket file", + socket_path.display() + ))); + } + + Ok(()) +} + +fn configure_socket_group( + socket_path: &Path, + socket_group: Option<&str>, +) -> Result<(), CommunicationMiddlewareError> { + if let Some(group_name) = socket_group { + // [impl->swdd~server-configures-unix-domain-socket-group~1] + let group = Group::from_name(group_name) + .map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not resolve unix socket group '{}': {err}", + group_name + )) + })? + .ok_or_else(|| { + CommunicationMiddlewareError(format!( + "Could not resolve unix socket group '{}': group does not exist", + group_name + )) + })?; + + chown(socket_path, None, Some(group.gid)).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not set unix socket group '{}' on '{}': {err}", + group_name, + socket_path.display() + )) + })?; + + fs::set_permissions(socket_path, fs::Permissions::from_mode(0o660)).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not set unix socket permissions on '{}': {err}", + socket_path.display() + )) + })?; + } + + Ok(()) +} + +////////////////////////////////////////////////////////////////////////////// +// ######## ####### ######### ######### // +// ## ## ## ## // +// ## ##### ######### ## // +// ## ## ## ## // +// ## ####### ######### ## // +////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use self::shim::{GROUP_GID, GROUP_NAME}; + use super::configure_socket_group; + use super::prepare_socket_path; + use nix::errno::Errno; + use nix::unistd::Gid; + use std::io; + use std::path::PathBuf; + use std::sync::{Mutex, OnceLock}; + + fn socket_path() -> PathBuf { + PathBuf::from(shim::SHARED_SOCKET_PATH) + } + + fn lock_shim() -> std::sync::MutexGuard<'static, ()> { + static TEST_LOCK: OnceLock> = OnceLock::new(); + TEST_LOCK + .get_or_init(|| Mutex::new(())) + .lock() + .expect("test lock poisoned") + } + + // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + #[test] + fn utest_prepare_unix_listener_skips_existing_path_checks_when_path_is_missing() { + let _lock = lock_shim(); + shim::reset_overrides(); + + let socket_path = socket_path(); + + let result = prepare_socket_path(&socket_path, false); + + assert!(result.is_ok()); + shim::assert_calls(&[]); + shim::reset_overrides(); + } + + // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + #[test] + fn utest_prepare_unix_listener_replaces_stale_socket_file() { + let _lock = lock_shim(); + shim::reset_overrides(); + + let socket_path = socket_path(); + shim::expect_metadata_once(&socket_path, Ok(shim::fs::Metadata::socket())); + shim::expect_remove_file_once(&socket_path, Ok(())); + + let result = prepare_socket_path(&socket_path, true); + + assert!(result.is_ok()); + shim::assert_calls(&["metadata", "remove_file"]); + shim::reset_overrides(); + } + + // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + #[test] + fn utest_prepare_unix_listener_fails_when_removing_stale_socket_fails() { + let _lock = lock_shim(); + shim::reset_overrides(); + + let socket_path = socket_path(); + shim::expect_metadata_once(&socket_path, Ok(shim::fs::Metadata::socket())); + + shim::expect_remove_file_once(&socket_path, Err(io::Error::other("remove failed"))); + + let result = prepare_socket_path(&socket_path, true); + + assert!(result.is_err()); + assert!( + result + .err() + .unwrap() + .0 + .contains("Could not remove stale unix socket") + ); + shim::assert_calls(&["metadata", "remove_file"]); + + shim::reset_overrides(); + } + + // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + #[test] + fn utest_prepare_unix_listener_fails_if_path_exists_and_is_not_socket() { + let _lock = lock_shim(); + shim::reset_overrides(); + + let file_path = socket_path(); + shim::expect_metadata_once(&file_path, Ok(shim::fs::Metadata::not_socket())); + + let result = prepare_socket_path(&file_path, true); + + assert!(result.is_err()); + assert!( + result + .err() + .unwrap() + .0 + .contains("exists and is not a socket file") + ); + shim::assert_calls(&["metadata"]); + + shim::reset_overrides(); + } + + // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + #[test] + fn utest_prepare_unix_listener_fails_when_existing_path_metadata_fails() { + let _lock = lock_shim(); + shim::reset_overrides(); + + let socket_path = socket_path(); + + shim::expect_metadata_once( + &socket_path, + Err(io::Error::new(io::ErrorKind::PermissionDenied, "denied")), + ); + + let result = prepare_socket_path(&socket_path, true); + + assert!(result.is_err()); + assert!( + result + .err() + .unwrap() + .0 + .contains("Could not access existing unix socket path") + ); + shim::assert_calls(&["metadata"]); + + shim::reset_overrides(); + } + + // [utest->swdd~server-configures-unix-domain-socket-group~1] + #[test] + fn utest_prepare_unix_listener_skips_group_configuration_without_group() { + let _lock = lock_shim(); + shim::reset_overrides(); + + let socket_path = socket_path(); + + let result = configure_socket_group(&socket_path, None); + + assert!(result.is_ok()); + shim::assert_calls(&[]); + + shim::reset_overrides(); + } + + // [utest->swdd~server-configures-unix-domain-socket-group~1] + #[test] + fn utest_prepare_unix_listener_fails_for_unknown_group() { + let _lock = lock_shim(); + shim::reset_overrides(); + let socket_path = socket_path(); + shim::expect_group_from_name_once(GROUP_NAME, Ok(None)); + + let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + + assert!(result.is_err()); + assert!(result.err().unwrap().0.contains("group does not exist")); + shim::assert_calls(&["group_from_name"]); + + shim::reset_overrides(); + } + + // [utest->swdd~server-configures-unix-domain-socket-group~1] + #[test] + fn utest_prepare_unix_listener_configures_group_permissions() { + let _lock = lock_shim(); + shim::reset_overrides(); + + let socket_path = socket_path(); + let gid = Gid::from_raw(GROUP_GID); + + shim::expect_group_from_name_once(GROUP_NAME, Ok(Some(shim::Group { gid }))); + shim::expect_chown_once(&socket_path, gid, Ok(())); + shim::expect_set_permissions_once(&socket_path, Ok(())); + + let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + + assert!(result.is_ok()); + shim::assert_calls(&["group_from_name", "chown", "set_permissions"]); + + shim::reset_overrides(); + } + + // [utest->swdd~server-configures-unix-domain-socket-group~1] + #[test] + fn utest_prepare_unix_listener_fails_when_chown_fails() { + let _lock = lock_shim(); + shim::reset_overrides(); + + let socket_path = socket_path(); + let gid = Gid::from_raw(GROUP_GID); + + shim::expect_group_from_name_once(GROUP_NAME, Ok(Some(shim::Group { gid }))); + shim::expect_chown_once(&socket_path, gid, Err(Errno::EPERM)); + + let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + + assert!(result.is_err()); + assert!( + result + .err() + .unwrap() + .0 + .contains("Could not set unix socket group") + ); + shim::assert_calls(&["group_from_name", "chown"]); + + shim::reset_overrides(); + } + + #[test] + fn utest_prepare_unix_listener_fails_when_group_lookup_fails() { + let _lock = lock_shim(); + shim::reset_overrides(); + let socket_path = socket_path(); + shim::expect_group_from_name_once(GROUP_NAME, Err(Errno::EINVAL)); + + let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + + assert!(result.is_err()); + assert!( + result + .err() + .unwrap() + .0 + .contains("Could not resolve unix socket group") + ); + shim::assert_calls(&["group_from_name"]); + + shim::reset_overrides(); + } + + // [utest->swdd~server-configures-unix-domain-socket-group~1] + #[test] + fn utest_prepare_unix_listener_fails_when_setting_permissions_fails() { + let _lock = lock_shim(); + shim::reset_overrides(); + + let socket_path = socket_path(); + let gid = Gid::from_raw(GROUP_GID); + + shim::expect_group_from_name_once(GROUP_NAME, Ok(Some(shim::Group { gid }))); + shim::expect_chown_once(&socket_path, gid, Ok(())); + shim::expect_set_permissions_once( + &socket_path, + Err(io::Error::other("set permissions failed")), + ); + + let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + + assert!(result.is_err()); + assert!( + result + .err() + .unwrap() + .0 + .contains("Could not set unix socket permissions") + ); + shim::assert_calls(&["group_from_name", "chown", "set_permissions"]); + + shim::reset_overrides(); + } + + pub(crate) mod shim { + use nix::unistd::Gid; + use std::io; + use std::path::Path; + use std::sync::{Mutex, MutexGuard, OnceLock}; + + pub(crate) const SHARED_SOCKET_PATH: &str = "/unit-test/shared.sock"; + pub(crate) const GROUP_NAME: &str = "ankaios"; + pub(crate) const GROUP_GID: u32 = 1000; + + struct Ops { + metadata: Option>, + remove_file: Option>, + set_permissions: Option>, + group_from_name: Option, nix::Error>>, + chown: Option>, + calls: Vec<&'static str>, + } + + fn ops_store() -> MutexGuard<'static, Ops> { + static STORE: OnceLock> = OnceLock::new(); + STORE + .get_or_init(|| { + Mutex::new(Ops { + metadata: None, + remove_file: None, + set_permissions: None, + group_from_name: None, + chown: None, + calls: Vec::new(), + }) + }) + .lock() + .expect("test shim lock poisoned") + } + + pub(crate) fn reset_overrides() { + let mut ops = ops_store(); + *ops = Ops { + metadata: None, + remove_file: None, + set_permissions: None, + group_from_name: None, + chown: None, + calls: Vec::new(), + }; + } + + pub(crate) fn assert_calls(expected: &[&'static str]) { + let ops = ops_store(); + assert_eq!(ops.calls.as_slice(), expected, "unexpected call sequence"); + } + + pub(crate) fn expect_metadata_once(path: &Path, result: io::Result) { + assert_eq!( + path, + Path::new(SHARED_SOCKET_PATH), + "unexpected metadata path" + ); + ops_store().metadata = Some(result); + } + + pub(crate) fn expect_remove_file_once(path: &Path, result: io::Result<()>) { + assert_eq!( + path, + Path::new(SHARED_SOCKET_PATH), + "unexpected remove_file path" + ); + ops_store().remove_file = Some(result); + } + + pub(crate) fn expect_set_permissions_once(path: &Path, result: io::Result<()>) { + assert_eq!( + path, + Path::new(SHARED_SOCKET_PATH), + "unexpected set_permissions path" + ); + ops_store().set_permissions = Some(result); + } + + pub(crate) fn expect_group_from_name_once( + name: &str, + result: Result, nix::Error>, + ) { + assert_eq!(name, GROUP_NAME, "unexpected group name lookup"); + ops_store().group_from_name = Some(result); + } + + pub(crate) fn expect_chown_once(path: &Path, gid: Gid, result: Result<(), nix::Error>) { + assert_eq!(path, Path::new(SHARED_SOCKET_PATH), "unexpected chown path"); + assert_eq!(gid, Gid::from_raw(GROUP_GID), "unexpected chown gid"); + ops_store().chown = Some(result); + } + + pub(crate) mod fs { + use super::{Path, SHARED_SOCKET_PATH, ops_store}; + use std::io; + + #[derive(Clone, Copy)] + pub struct Permissions { + pub mode: u32, + } + + impl Permissions { + pub fn from_mode(mode: u32) -> Self { + Self { mode } + } + } + + #[derive(Clone, Copy)] + pub struct FileType { + pub is_socket: bool, + } + + impl FileType { + pub fn is_socket(&self) -> bool { + self.is_socket + } + } + + #[derive(Clone, Copy)] + pub struct Metadata { + pub is_socket: bool, + } + + impl Metadata { + pub fn socket() -> Self { + Self { is_socket: true } + } + + pub fn not_socket() -> Self { + Self { is_socket: false } + } + + pub fn file_type(&self) -> FileType { + FileType { + is_socket: self.is_socket, + } + } + } + + pub fn metadata(path: &Path) -> io::Result { + let mut ops = ops_store(); + ops.calls.push("metadata"); + assert_eq!( + path, + Path::new(SHARED_SOCKET_PATH), + "unexpected metadata path" + ); + if let Some(result) = ops.metadata.take() { + return result; + } + panic!("missing metadata expectation for '{}'", path.display()); + } + + pub fn remove_file(path: &Path) -> io::Result<()> { + let mut ops = ops_store(); + ops.calls.push("remove_file"); + assert_eq!( + path, + Path::new(SHARED_SOCKET_PATH), + "unexpected remove_file path" + ); + if let Some(result) = ops.remove_file.take() { + return result; + } + panic!("missing remove_file expectation for '{}'", path.display()); + } + + pub fn set_permissions(path: &Path, perms: Permissions) -> io::Result<()> { + let mut ops = ops_store(); + ops.calls.push("set_permissions"); + assert_eq!(perms.mode, 0o660, "unexpected permission mode"); + assert_eq!( + path, + Path::new(SHARED_SOCKET_PATH), + "unexpected set_permissions path" + ); + if let Some(result) = ops.set_permissions.take() { + return result; + } + panic!( + "missing set_permissions expectation for '{}'", + path.display() + ); + } + } + + #[derive(Clone, Copy)] + pub(crate) struct Group { + pub gid: Gid, + } + + impl Group { + pub fn from_name(group_name: &str) -> Result, nix::Error> { + let mut ops = ops_store(); + ops.calls.push("group_from_name"); + assert_eq!(group_name, GROUP_NAME, "unexpected group name lookup"); + if let Some(result) = ops.group_from_name.take() { + return result; + } + panic!("missing group lookup expectation for '{group_name}'"); + } + } + + pub fn chown( + path: &Path, + _uid: Option, + gid: Option, + ) -> Result<(), nix::Error> { + let gid = gid.expect("gid is required for test shim chown"); + let mut ops = ops_store(); + ops.calls.push("chown"); + if let Some(result) = ops.chown.take() { + assert_eq!(path, Path::new(SHARED_SOCKET_PATH), "unexpected chown path"); + assert_eq!(gid, Gid::from_raw(GROUP_GID), "unexpected chown gid"); + return result; + } + panic!("missing chown expectation for '{}'", path.display()); + } + } +} From 51a7dd1fa9522ce235e9657d198ed154715a8050 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Fri, 11 Sep 2026 12:23:17 +0200 Subject: [PATCH 17/55] Exclude unused trait imports from test --- grpc/src/server_unix_listener.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grpc/src/server_unix_listener.rs b/grpc/src/server_unix_listener.rs index 5b17a7cbe..88ab3cf8c 100644 --- a/grpc/src/server_unix_listener.rs +++ b/grpc/src/server_unix_listener.rs @@ -20,7 +20,9 @@ use self::tests::shim::{Group, chown, fs}; use nix::unistd::{Group, chown}; #[cfg(not(test))] use std::fs; +#[cfg(not(test))] use std::os::unix::fs::FileTypeExt; +#[cfg(not(test))] use std::os::unix::fs::PermissionsExt; use std::path::Path; use tokio::net::UnixListener; From 1c22724399c68de34962e7e3c5487827f8387290 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 16 Sep 2026 11:18:01 +0200 Subject: [PATCH 18/55] Adapt stests --- tests/resources/ankaios.resource | 81 ++++++------------- tests/resources/configs/ank-server.conf | 49 ----------- tests/resources/variables.resource | 3 + tests/stests/agents/list_agents.robot | 4 +- tests/stests/agents/update_agents.robot | 6 +- tests/stests/configs/config_file.robot | 2 +- tests/stests/configs/work_with_configs.robot | 6 +- .../control_interface/control_interface.robot | 6 +- tests/stests/events/get_events.robot | 16 ++-- tests/stests/manifests/apply_manifests.robot | 8 +- tests/stests/mtls/mtls_communication.robot | 8 +- .../delete_workload_containerd.robot | 2 +- .../workloads/delete_workload_podman.robot | 2 +- .../delete_workload_podman_kube.robot | 2 +- .../interworkload_dependencies.robot | 14 ++-- tests/stests/workloads/list_workloads.robot | 2 +- ...eject_state_with_cyclic_dependencies.robot | 2 +- .../restart_handling_containerd.robot | 6 -- .../workloads/restart_handling_podman.robot | 6 -- .../restart_handling_podman_kube.robot | 6 -- ...etry_creation_of_workload_containerd.robot | 8 +- .../retry_creation_of_workload_podman.robot | 8 +- .../state_operations_with_field_mask.robot | 4 +- .../update_workload_containerd.robot | 4 +- .../workloads/update_workload_podman.robot | 40 ++++----- .../workloads/workload_configs_podman.robot | 8 +- .../workload_configs_podman_kube.robot | 5 -- tests/stests/workloads/workload_files.robot | 6 +- 28 files changed, 107 insertions(+), 207 deletions(-) diff --git a/tests/resources/ankaios.resource b/tests/resources/ankaios.resource index 243827212..e667e2b6b 100644 --- a/tests/resources/ankaios.resource +++ b/tests/resources/ankaios.resource @@ -34,7 +34,6 @@ ${TEST_FOLDER_NAME} ${EMPTY} &{ANKAIOS_INSTANCE_NAME_TO_PODMAN_ID_MAPPING} &{ANKAIOS_WORKLOAD_NAME_TO_PODMAN_ID_MAPPING} ${ANKAIOS_VERSION} ${EMPTY} -${CONFIGS_DIR}= ${CURDIR}/configs *** Keywords *** @@ -55,6 +54,21 @@ Setup Ankaios Setup MTLS enabled=${mtls_enabled} Setup Ankaios without MTLS Setup +Setup Ankaios for mTLS with explicit certs + Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_TCP_ADDRESS} + Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} + Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} + Setup Ankaios without MTLS Setup + +Setup Ankaios for insecure TCP + Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_TCP_ADDRESS} + Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} + Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} + Set Environment Variable name=ANKSERVER_INSECURE value=true + Set Environment Variable name=ANKAGENT_INSECURE value=true + Set Environment Variable name=ANK_INSECURE value=true + Setup Ankaios without MTLS Setup + Clean up Ankaios Unsubscribe From Events Terminate All Processes kill=True @@ -77,6 +91,9 @@ Clean up Ankaios Remove Environment Variable ANKSERVER_INSECURE Remove Environment Variable ANKAGENT_INSECURE Remove Environment Variable ANK_INSECURE + Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_URL} + Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_URL} + Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_URL} Clean up Podman Log Clean up Podman @@ -91,6 +108,9 @@ Clean up containerd Setup MTLS [Arguments] ${enabled} IF ${enabled} == True + Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_TCP_ADDRESS} + Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} + Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} ${ca_pem}= Catenate SEPARATOR= ${CERTS_DIR} /ca.pem ${server_crt_pem}= Catenate SEPARATOR= ${CERTS_DIR} /server.pem ${server_key_pem}= Catenate SEPARATOR= ${CERTS_DIR} /server-key.pem @@ -107,20 +127,9 @@ Setup MTLS Set Environment Variable name=ANK_CA_PEM value=${ca_pem} Set Environment Variable name=ANK_CRT_PEM value=${cli_crt_pem} Set Environment Variable name=ANK_KEY_PEM value=${cli_key_pem} - ELSE - Set Environment Variable name=ANKSERVER_INSECURE value=true - Set Environment Variable name=ANKAGENT_INSECURE value=true - Set Environment Variable name=ANK_INSECURE value=true END -Run in background - [Arguments] ${location} ${cli_command} - Log Run in background: ${cli_command} - ${cmd}= Split Command Line ${cli_command} - Set List Value ${cmd} 0 ${location}${/}${cmd}[0] - Start Process @{cmd} - Run in foreground [Arguments] ${cli_command} Log Run in foreground: ${cli_command} @@ -413,12 +422,6 @@ in the last result, the workload "${workload_name}" shall have the execution sta ... ${list_result} ... msg=Timeout: Workload '${workload_name}' on agent '${expected_agent_name}' does not have the execution state '${expected_execution_state}'! -user executes system app "${cli_command}" - ${cmd}= Split Command Line ${cli_command} escaping=True - ${CURRENT_RESULT}= Run Process @{cmd} timeout=20 shell=True stdout=${TEMPDIR}/stdout.txt stderr=${TEMPDIR}/stderr.txt - Log ${CURRENT_RESULT.stdout} - Set Global Variable ${CURRENT_RESULT} - volumes for "${workload_name}" shall exist on "${agent_name}" within "${timeout}" seconds ${start_time}= Get Time Secs ${volume_names}= Create List @@ -718,25 +721,15 @@ The controller workload can receive logs from ${workload_names} internal_log_allow_control_interface ${workload_names} The controller workload sends initial hello with correct version -The controller workload cannot receive logs from ${workload_names} - internal_log_deny_control_interface ${workload_names} - The controller workload sends initial hello with correct version - The controller workload requests the logs of ${workload_names_with_agents} internal_add_logs_request_command ${workload_names_with_agents} The controller workload gets the logs of ${workload_names} internal_get_logs_command ${workload_names} -The controller workload cancels the logs request of ${workload_names} - internal_add_cancel_logs_request_command ${workload_names} - The controller workload sends initial hello with correct version internal_send_initial_hello ${ANKAIOS_VERSION} -The controller workload updates the state with manifest "${manifest}" - The controller workload updates the state with manifest "${manifest}" and update mask ${EMPTY} - The controller workload updates the state with manifest "${manifest}" and update mask ${update_mask} internal_add_update_state_command ${manifest} ${update_mask} @@ -758,9 +751,6 @@ The last result contains exactly the workloads ${workload_names} The last result contains no workloads internal_check_control_interface_workloads_in_last_result ${EMPTY} -In the last result the workload ${workload_name} has exactly the fields ${field_names} - internal_check_control_interface_workload_fields_in_last_result ${workload_name} ${field_names} - The last result has added fields ${field_names} internal_check_control_interface_altered_fields_in_last_result addedFields ${field_names} @@ -807,7 +797,7 @@ The controller workload requests shall all succeed with second agent The controller workload shall execute successfully on running system ${ankaios_config_folder}= create_control_interface_config_for_test - user triggers "ank -k apply ${ankaios_config_folder.name}${/}startup_config.yaml" + user triggers "ank apply ${ankaios_config_folder.name}${/}startup_config.yaml" And the workload "controller" shall have the execution state "Succeeded(Ok)" on agent "${AGENT_NAME}" within "20" seconds internal_check_all_control_interface_requests_succeeded ${ankaios_config_folder.name} @@ -902,20 +892,6 @@ Get Running Container Names RETURN ${container_names} -Log Container Debugging Information - [Documentation] Logs debugging information about workloads, pods, and containers - # Debug: Check workload status - ${command_result}= Run Command %{ANK_BIN_DIR}${/}ank get workloads - Log Ankaios workloads: ${command_result.stdout} - - # Debug: Check podman pods - ${result}= Run Process podman pod ls shell=True - Log Podman pods: ${result.stdout} - - # Debug: Check all containers (running and stopped) - ${result}= Run Process podman ps -a shell=True - Log All containers: ${result.stdout} - Identify Target Container For Control Interface [Arguments] ${container_names} ${target_container_name}=container_A ${pod_name}=pod_A [Documentation] Identifies the target container and non-target containers based on naming patterns @@ -970,11 +946,10 @@ Verify Control Interface Access In Containers END Verify Multi Container Control Interface Access - [Arguments] ${workload_name}=simple ${container_name}=container_A ${pod_name}=pod_A + [Arguments] ${container_name}=container_A ${pod_name}=pod_A [Documentation] Verifies that control interface access is properly limited in multi-container environments ${container_names}= Get Running Container Names - ${container_count}= Get Length ${container_names} ${target_container} ${non_target_containers}= Identify Target Container For Control Interface ${container_names} ${container_name} ${pod_name} Verify Control Interface Access In Containers ${target_container} ${non_target_containers} @@ -1058,9 +1033,6 @@ containerd shall have a container for workload "${workload_name}" on agent "${ag containerd shall have a container for workload "${workload_name}" with custom name "${name}" on agent "${agent_name}" runtime "containerd" shall have a container for workload "${workload_name}" with custom name "${name}" on agent "${agent_name}" within "1" seconds -containerd shall not have a container for workload "${workload_name}" on agent "${agent_name}" - runtime "containerd" shall not have a container for workload "${workload_name}" on agent "${agent_name}" within "1" seconds - containerd shall not have a container for workload "${workload_name}" on agent "${agent_name}" within "${timeout}" seconds runtime "containerd" shall not have a container for workload "${workload_name}" on agent "${agent_name}" within "${timeout}" seconds @@ -1078,12 +1050,12 @@ the last command shall finish with exit code "${exit_code}" user starts the CLI to subscribe to events with format "${format}" and field mask "${field_mask}" in background ${field_mask_arg}= Set Variable If "${field_mask}" == "" ${EMPTY} ${field_mask} - ${cmd}= Set Variable If "${field_mask}" == "" %{ANK_BIN_DIR}${/}ank -k get events -o ${format} %{ANK_BIN_DIR}${/}ank -k get events \"${field_mask_arg}\" -o ${format} + ${cmd}= Set Variable If "${field_mask}" == "" %{ANK_BIN_DIR}${/}ank get events -o ${format} %{ANK_BIN_DIR}${/}ank get events \"${field_mask_arg}\" -o ${format} Start Process ${cmd} shell=True stdout=target/robot_tests_result/${TEST_FOLDER_NAME}/events_output.txt stderr=target/robot_tests_result/${TEST_FOLDER_NAME}/events_error.txt alias=event-listener user starts the CLI to subscribe to events with format "${format}", field mask "${field_mask}" and current state output enabled in background ${field_mask_arg}= Set Variable If "${field_mask}" == "" ${EMPTY} ${field_mask} - ${cmd}= Set Variable If "${field_mask}" == "" %{ANK_BIN_DIR}${/}ank -k get events -o ${format} %{ANK_BIN_DIR}${/}ank -k get events \"${field_mask_arg}\" --include-current-state -o ${format} + ${cmd}= Set Variable If "${field_mask}" == "" %{ANK_BIN_DIR}${/}ank get events -o ${format} %{ANK_BIN_DIR}${/}ank get events \"${field_mask_arg}\" --include-current-state -o ${format} Start Process ${cmd} shell=True stdout=target/robot_tests_result/${TEST_FOLDER_NAME}/events_output.txt stderr=target/robot_tests_result/${TEST_FOLDER_NAME}/events_error.txt alias=event-listener the CLI listens for workload states @@ -1104,9 +1076,6 @@ the event output shall be valid yaml format the event output shall be valid json format Event Output Shall Be Valid Json Format target/robot_tests_result/${TEST_FOLDER_NAME}/events_output.txt -the event output shall contain timestamp - Event Output Shall Contain Timestamp target/robot_tests_result/${TEST_FOLDER_NAME}/events_output.txt - the event output shall contain timestamp in RFC3339 format Event Output Shall Contain Timestamp In RFC3339 Format target/robot_tests_result/${TEST_FOLDER_NAME}/events_output.txt diff --git a/tests/resources/configs/ank-server.conf b/tests/resources/configs/ank-server.conf index 4b5538ae2..bb88aaa54 100644 --- a/tests/resources/configs/ank-server.conf +++ b/tests/resources/configs/ank-server.conf @@ -1,51 +1,2 @@ -# This is the configuration file for an Ankaios server. -# This configuration file is formatted in the TOML language. - -# The format version of the configuration file to ensure compatibility. version = 'v1' - -# The path to the startup configuration manifest file. -# By default, no startup configuration manifest is used. startup_manifest = 'tests/resources/configs/simple.yaml' - -# The address, including the port, to which the server should listen. -address = '127.0.0.1:25551' - -# The flag to disable TLS communication between -# the Ankaios server, agents and the ank CLI. -# If set to 'true', then the server shall not use TLS. -insecure = true - -# The path to ca certificate pem file. -# ca_pem = '''/tmp/.certs/ca.pem''' -# The path to server certificate pem file. -# crt_pem = '''/tmp/.certs/ank-server.pem''' -# The path to server key pem file. -# key_pem = '''/tmp/.certs/ank-server-key.pem''' - -# The content of the ca certificate pem file. -# You can either provide ca_pem or ca_pem_content, but not both. -# ca_pem_content = '''-----BEGIN CERTIFICATE----- -# MIHkMIGXAhRrcu4Zx8sBdvQZ53KiUCAV2/DSzjAFBgMrZXAwFTETMBEGA1UEAwwK -# YW5rYWlvcy1jYTAeFw0yNDEyMTIwODE3MjJaFw0yNTAxMTEwODE3MjJaMBUxEzAR -# BgNVBAMMCmFua2Fpb3MtY2EwKjAFBgMrZXADIQBiiJvCIsEl4yf1Iz2e30Fd/wDT -# rxivpuUR8Fvf7VzW8zAFBgMrZXADQQAbyzE6hUvTfQoTcsw3WAsKzyPMlDNhRCLT -# PSyvp3Pom3z7CEDlD9Oxya7j2DGBuMNN/tkcKv97KPKDg7cuVhEK -# -----END CERTIFICATE-----''' -# The content of the server certificate pem file. -# You can either provide crt_pem or crt_pem_content, but not both. -# crt_pem_content = '''-----BEGIN CERTIFICATE----- -# MIIBdzCCASmgAwIBAgIUcc+fYH1jfenKG0VjaK/dq2AQYX4wBQYDK2VwMBUxEzAR -# BgNVBAMMCmFua2Fpb3MtY2EwHhcNMjQxMjEyMDgxODE1WhcNMjUwMTExMDgxODE1 -# WjAVMRMwEQYDVQQDDAphbmstc2VydmVyMCowBQYDK2VwAyEAIRHLwFrVl5q/zxSF -# 1VVbdeLOkVWU122AVJKZp+XWEGejgYowgYcwFQYDVR0RBA4wDIIKYW5rLXNlcnZl -# cjATBgNVHSUEDDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUX29zFqtNOoy/Gz5Iub+/ -# Vk3PhiIwOgYDVR0jBDMwMaEZpBcwFTETMBEGA1UEAwwKYW5rYWlvcy1jYYIUa3Lu -# GcfLAXb0GedyolAgFdvw0s4wBQYDK2VwA0EAvYqIJrJlUDn3G1+SXHmN/yJwV7p1 -# iUBL6ntNOPJwjTXfMCdIAi8seVAC2q1TF4JSwV5dWyrb26zYpIDfd92sDQ== -# -----END CERTIFICATE-----''' -# The content of the server key pem file. -# You can either provide key_pem or key_pem_content, but not both. -# key_pem_content = '''-----BEGIN PRIVATE KEY----- -# MC4CAQAwBQYDK2VwBCIEIHReRRplNTGtXExp5HVNqX8+x1obKib1H2d6fHGK7gZD -# -----END PRIVATE KEY-----''' diff --git a/tests/resources/variables.resource b/tests/resources/variables.resource index 960d6a984..7fa2887a7 100644 --- a/tests/resources/variables.resource +++ b/tests/resources/variables.resource @@ -18,3 +18,6 @@ ${CERTS_DIR}= /tmp/.certs ${AGENT_NAME}= agent_A ${AGENT_2_NAME}= agent_B ${ANKAIOS_TMP_FOLDER}= /tmp/ankaios +${ANKAIOS_SERVER_URL}= unix:///tmp/ankaios.sock +${ANKAIOS_SERVER_TCP_ADDRESS}= 127.0.0.1:25551 +${ANKAIOS_SERVER_HTTPS_URL}= https://127.0.0.1:25551 diff --git a/tests/stests/agents/list_agents.robot b/tests/stests/agents/list_agents.robot index 8347f1cca..a105e5a01 100644 --- a/tests/stests/agents/list_agents.robot +++ b/tests/stests/agents/list_agents.robot @@ -35,7 +35,7 @@ Test Ankaios CLI lists connected agents And the workload "hello2" shall have the execution state "Pending(Initial)" on agent "agent_B" And the workload "hello3" shall have the execution state "Pending(Initial)" on agent "agent_B" # Actions - When user triggers "ank -k get agents" + When user triggers "ank get agents" # Asserts Then the last command shall list the connected agent "agent_A" @@ -52,7 +52,7 @@ Test Ankaios CLI enforces agent naming convention And Ankaios agent is started with name "agent.A" And Ankaios agent is started with config file "${CONFIGS_DIR}/ank-agent_invalid_agent_name.conf" # Actions - When user triggers "ank -k get agents" + When user triggers "ank get agents" ${result_cli}= Run Keyword And Return Status the last command shall list the connected agent "agent.A" ${result_config}= Run Keyword And Return Status the last command shall list the connected agent "Invalid@gent.name" # Asserts diff --git a/tests/stests/agents/update_agents.robot b/tests/stests/agents/update_agents.robot index 462236dcd..6cf95d26f 100644 --- a/tests/stests/agents/update_agents.robot +++ b/tests/stests/agents/update_agents.robot @@ -33,7 +33,7 @@ Test Ankaios updates only agent tags through state updates And Ankaios agent is started with name "${agent_name}" and tags "type=AI-agent location=online" And the CLI listens for workload states # Verify initial tags via CLI - When user triggers "ank -k get state" + When user triggers "ank get state" Then the agent "${agent_name}" shall have tag "type" with value "AI-agent" And the agent "${agent_name}" shall have tag "location" with value "online" @@ -43,11 +43,11 @@ Test Ankaios updates only agent tags through state updates And the controller workload shall execute successfully on running system # Verify updated state - When user triggers "ank -k get agents" + When user triggers "ank get agents" Then the last command shall list exactly "1" agents And the last command shall list the connected agent "${agent_name}" - When user triggers "ank -k get state" + When user triggers "ank get state" Then the agent "${agent_name}" shall have tag "location" with value "on-car" And the agent "${agent_name}" shall have tag "new_tag" with value "value" And the agent "${agent_name}" shall not have tag "type" diff --git a/tests/stests/configs/config_file.robot b/tests/stests/configs/config_file.robot index c88412068..757ea2f44 100644 --- a/tests/stests/configs/config_file.robot +++ b/tests/stests/configs/config_file.robot @@ -36,7 +36,7 @@ Test agent config file successful start-up And Ankaios server is available And Ankaios agent is started with config file "${CONFIGS_DIR}/ank-agent-default.conf" # Actions - When user triggers "ank -k get agents" + When user triggers "ank get agents" ${result_config}= Run Keyword And Return Status the last command shall list the connected agent "agent_1" # Asserts Pass Execution If ${result_config} == False The agent name "Invalid@gent.name" is not allowed diff --git a/tests/stests/configs/work_with_configs.robot b/tests/stests/configs/work_with_configs.robot index df1eb635f..20cd198f1 100644 --- a/tests/stests/configs/work_with_configs.robot +++ b/tests/stests/configs/work_with_configs.robot @@ -27,7 +27,7 @@ Test Ankaios CLI get configs And Ankaios server is started with manifest "${CONFIGS_DIR}/default_with_config.yaml" And Ankaios server is available # Actions - When user triggers "ank -k get configs" + When user triggers "ank get configs" # Asserts Then the last command shall list the config "config_1" And the last command shall list the config "config_2" @@ -42,8 +42,8 @@ Test Ankaios Podman remove configs And Ankaios server is started with manifest "${CONFIGS_DIR}/default_with_config.yaml" And Ankaios server is available # Actions - When user triggers "ank -k delete configs config_1" - And user triggers "ank -k get configs" + When user triggers "ank delete configs config_1" + And user triggers "ank get configs" # Asserts Then the last command shall not list the config "config_1" And the last command shall list the config "config_2" diff --git a/tests/stests/control_interface/control_interface.robot b/tests/stests/control_interface/control_interface.robot index 5d71d4393..0114c7339 100644 --- a/tests/stests/control_interface/control_interface.robot +++ b/tests/stests/control_interface/control_interface.robot @@ -48,7 +48,7 @@ Test Ankaios workload restart after update without a Control Interface access And Ankaios agent is started with name "${agent_name}" And the workload "${workload_name}" shall have the execution state "Succeeded(Ok)" on agent "${agent_name}" # Actions - When user triggers "ank -k apply ${CONFIGS_DIR}/simple.yaml" + When user triggers "ank apply ${CONFIGS_DIR}/simple.yaml" # Asserts Then the mount point for the control interface has not been generated for ${agent_name} [Teardown] Clean up Ankaios @@ -158,7 +158,7 @@ Test target path from control interface access is limited to the designated pod And the workload "${workload_name}" shall have the execution state "Running(Ok)" on agent "${agent_name}" within "30" seconds And the mount point for the control interface has been generated for ${agent_name} # Asserts - Then verify multi container control interface access simple + Then verify multi container control interface access [Teardown] Clean up Ankaios # [stest->swdd~podman-kube-mounts-control-interface~2] @@ -171,5 +171,5 @@ Test target path from control interface access is limited to the designated depl And the workload "simple" shall have the execution state "Running(Ok)" on agent "${agent_name}" within "60" seconds And the mount point for the control interface has been generated for ${agent_name} # Asserts - Then verify multi container control interface access simple container_A pod_A-pod + Then verify multi container control interface access container_A pod_A-pod [Teardown] Clean up Ankaios diff --git a/tests/stests/events/get_events.robot b/tests/stests/events/get_events.robot index 8001a119e..e981e154e 100644 --- a/tests/stests/events/get_events.robot +++ b/tests/stests/events/get_events.robot @@ -36,7 +36,7 @@ Test Ankaios CLI get events with field mask filter And Ankaios agent is started with name "agent_A" # Actions When user starts the CLI to subscribe to events with format "yaml" and field mask "desiredState.workloads" in background - And user triggers "ank -k apply ${CONFIGS_DIR}/nginx.yaml" + And user triggers "ank apply ${CONFIGS_DIR}/nginx.yaml" And the user waits "3" seconds # Asserts Then the event output shall contain workload "nginx" @@ -79,9 +79,9 @@ Test Ankaios CLI get events receives multiple sequential events # Actions When user starts the CLI to subscribe to events with format "json" and field mask "" in background And the user waits "3" seconds - And user triggers "ank -k apply ${CONFIGS_DIR}/nginx.yaml" + And user triggers "ank apply ${CONFIGS_DIR}/nginx.yaml" And the user waits "3" seconds - And user triggers "ank -k apply ${CONFIGS_DIR}/manifest1.yaml" + And user triggers "ank apply ${CONFIGS_DIR}/manifest1.yaml" And the user waits "5" seconds # Asserts Then the event output shall contain at least "2" events @@ -97,7 +97,7 @@ Test Ankaios CLI get events handles connection errors gracefully # Preconditions # Actions - When user triggers "ank -k get events" + When user triggers "ank get events" # Asserts Then the last command shall finish with an error [Teardown] Clean up Ankaios @@ -117,7 +117,7 @@ Test Ankaios CLI get events with workload state changes # Actions When user starts the CLI to subscribe to events with format "yaml" and field mask "workloadStates" in background And the user waits "2" seconds - And user triggers "ank -k apply ${CONFIGS_DIR}/nginx.yaml" + And user triggers "ank apply ${CONFIGS_DIR}/nginx.yaml" And the user waits "3" seconds # Asserts Then the event output shall contain field name "workloadStates" @@ -139,7 +139,7 @@ Test Ankaios CLI get events includes timestamp in output # Actions When user starts the CLI to subscribe to events with format "json" and field mask "" in background And the user waits "3" seconds - And user triggers "ank -k apply ${CONFIGS_DIR}/nginx.yaml" + And user triggers "ank apply ${CONFIGS_DIR}/nginx.yaml" And the user waits "5" seconds # Asserts Then the event output shall contain timestamp in RFC3339 format @@ -160,7 +160,7 @@ Test Ankaios CLI get events with empty field mask # Actions When user starts the CLI to subscribe to events with format "yaml" and field mask "" in background And the user waits "3" seconds - And user triggers "ank -k apply ${CONFIGS_DIR}/nginx.yaml" + And user triggers "ank apply ${CONFIGS_DIR}/nginx.yaml" And the user waits "5" seconds # Asserts Then the event output shall contain field name "desiredState" @@ -184,7 +184,7 @@ Test Ankaios CLI get events with workload deletion # Actions When user starts the CLI to subscribe to events with format "json" and field mask "desiredState.workloads" in background And the user waits "3" seconds - And user triggers "ank -k delete workload sleepy hello1" + And user triggers "ank delete workload sleepy hello1" And the user waits "5" seconds # Asserts Then the event output shall contain altered fields with removed workloads diff --git a/tests/stests/manifests/apply_manifests.robot b/tests/stests/manifests/apply_manifests.robot index f15f42334..926b4c2d4 100644 --- a/tests/stests/manifests/apply_manifests.robot +++ b/tests/stests/manifests/apply_manifests.robot @@ -276,8 +276,8 @@ Test Ankaios Podman Update workload with current API version And Ankaios agent is started with name "agent_A" And the workload "sleepy" shall have the execution state "Running(Ok)" on agent "agent_A" # Actions - When user triggers "ank -k apply ${CONFIGS_DIR}/default.yaml" - And user triggers "ank -k get workloads" + When user triggers "ank apply ${CONFIGS_DIR}/default.yaml" + And user triggers "ank get workloads" # Asserts Then the last command shall finish with exit code "0" Then the workload "sleepy" shall have the execution state "Running(Ok)" on agent "agent_A" within "20" seconds @@ -297,8 +297,8 @@ Test Ankaios Podman Update workload with deprecated API version v0.1 And the CLI listens for workload states And Ankaios agent is started with name "agent_A" # Actions - When user triggers "ank -k apply ${CONFIGS_DIR}/default_deprecated_api.yaml" - And user triggers "ank -k get workloads" + When user triggers "ank apply ${CONFIGS_DIR}/default_deprecated_api.yaml" + And user triggers "ank get workloads" # Asserts Then the last command shall finish with exit code "0" Then the workload "sleepy" shall have the execution state "Running(Ok)" on agent "agent_A" within "20" seconds diff --git a/tests/stests/mtls/mtls_communication.robot b/tests/stests/mtls/mtls_communication.robot index f645de1fe..35a7d849c 100644 --- a/tests/stests/mtls/mtls_communication.robot +++ b/tests/stests/mtls/mtls_communication.robot @@ -45,7 +45,7 @@ Test Ankaios MTLS by providing PEM files via environment variables # [stest->swdd~agent-supports-pem-file-paths-as-cli-arguments~1] # [stest->swdd~cli-supports-pem-file-paths-as-cli-arguments~1] Test Ankaios MTLS by providing PEM files via command line arguments - [Setup] Run Keyword Setup Ankaios without MTLS Setup + [Setup] Run Keyword Setup Ankaios for mTLS with explicit certs # Preconditions # This test assumes that all containers in the podman have been created with this test -> clean it up first Given Podman has deleted all existing containers @@ -62,7 +62,7 @@ Test Ankaios MTLS by providing PEM files via command line arguments # [stest->swdd~agent-supports-pem-file-paths-as-cli-arguments~1] # [stest->swdd~cli-supports-pem-file-paths-as-cli-arguments~1] Test Ankaios MTLS by providing wrong PEM config via command line arguments - [Setup] Run Keyword Setup Ankaios without MTLS Setup + [Setup] Run Keyword Setup Ankaios for mTLS with explicit certs # Preconditions # This test assumes that all containers in the podman have been created with this test -> clean it up first Given Podman has deleted all existing containers @@ -81,7 +81,7 @@ Test Ankaios MTLS by providing wrong PEM config via command line arguments # [stest->swdd~cli-supports-cli-argument-for-insecure-communication~1] # [stest->swdd~cli-establishes-insecure-communication-based-on-provided-insecure-cli-argument~1] Test Ankaios insecure mode by providing --insecure command line arguments - [Setup] Run Keyword Setup Ankaios + [Setup] Run Keyword Setup Ankaios for insecure TCP # Preconditions # This test assumes that all containers in the podman have been created with this test -> clean it up first Given Podman has deleted all existing containers @@ -104,7 +104,7 @@ Test Ankaios insecure mode by providing --insecure command line arguments # [stest->swdd~agent-loads-config-file~2] # [stest->swdd~cli-loads-config-file~2] Test Ankaios MTLS by providing PEM files via config files - [Setup] Run Keyword Setup Ankaios without MTLS Setup + [Setup] Run Keyword Setup Ankaios for mTLS with explicit certs # Preconditions # This test assumes that all containers in the podman have been created with this test -> clean it up first Given Podman has deleted all existing containers diff --git a/tests/stests/workloads/delete_workload_containerd.robot b/tests/stests/workloads/delete_workload_containerd.robot index f1ea9e227..c1de362fb 100644 --- a/tests/stests/workloads/delete_workload_containerd.robot +++ b/tests/stests/workloads/delete_workload_containerd.robot @@ -37,7 +37,7 @@ Test Ankaios containerd remove workloads And Ankaios agent is started with name "agent_A" And the workload "sleepy" shall have the execution state "Running(Ok)" on agent "agent_A" # Actions - When user triggers "ank --no-wait -k delete workload sleepy" + When user triggers "ank --no-wait delete workload sleepy" # Asserts Then the workload "sleepy" shall be removed and not exist on agent "agent_A" within "20" seconds And the workload "hello2" shall have the execution state "Succeeded(Ok)" on agent "agent_B" within "20" seconds diff --git a/tests/stests/workloads/delete_workload_podman.robot b/tests/stests/workloads/delete_workload_podman.robot index 6b5f6c02e..5907701f3 100644 --- a/tests/stests/workloads/delete_workload_podman.robot +++ b/tests/stests/workloads/delete_workload_podman.robot @@ -37,7 +37,7 @@ Test Ankaios Podman remove workloads And Ankaios agent is started with name "agent_B" And the workload "sleepy" shall have the execution state "Running(Ok)" on agent "agent_A" # Actions - When user triggers "ank --no-wait -k delete workload sleepy" + When user triggers "ank --no-wait delete workload sleepy" # Asserts Then the workload "sleepy" shall be removed and not exist on agent "agent_A" within "20" seconds And the workload "hello1" shall have the execution state "Failed(Lost)" on agent "agent_B" within "20" seconds diff --git a/tests/stests/workloads/delete_workload_podman_kube.robot b/tests/stests/workloads/delete_workload_podman_kube.robot index cb0291bbb..6adb8c597 100644 --- a/tests/stests/workloads/delete_workload_podman_kube.robot +++ b/tests/stests/workloads/delete_workload_podman_kube.robot @@ -38,7 +38,7 @@ Test Ankaios Podman delete kube workload And Ankaios agent is started with name "agent_A" And the workload "hello-k8s" shall have the execution state "Running(Ok)" on agent "agent_A" # Actions - When user triggers "ank --no-wait -k delete workload hello-k8s" + When user triggers "ank --no-wait delete workload hello-k8s" # Asserts Then podman shall not have a container for workload "hello-pod-hello-container" on agent "agent_A" And volumes for "hello-k8s" shall not exist on "agent_A" within "20" seconds diff --git a/tests/stests/workloads/interworkload_dependencies.robot b/tests/stests/workloads/interworkload_dependencies.robot index d1eb35de3..696f4d590 100644 --- a/tests/stests/workloads/interworkload_dependencies.robot +++ b/tests/stests/workloads/interworkload_dependencies.robot @@ -65,9 +65,9 @@ Test Ankaios observes the inter-workload dependencies when deleting workloads And Ankaios agent is started with name "agent_A" And the workload "frontend" shall have the execution state "Running(Ok)" on agent "agent_A" within "20" seconds # Actions - When user triggers "ank -k --no-wait delete workload backend" + When user triggers "ank --no-wait delete workload backend" And the workload "backend" shall have the execution state "Stopping(WaitingToStop)" on agent "agent_A" - And user triggers "ank -k --no-wait delete workload frontend" + And user triggers "ank --no-wait delete workload frontend" # Asserts Then the workload "backend" shall be removed and not exist on agent "agent_A" within "20" seconds [Teardown] Clean up Ankaios @@ -88,11 +88,11 @@ Test Ankaios CLI update workload with pending delete And Ankaios agent is started with name "agent_A" And the workload "frontend" shall have the execution state "Running(Ok)" on agent "agent_A" within "20" seconds # Actions - When user triggers "ank -k get state > ${new_state_yaml_file}" + When user triggers "ank get state > ${new_state_yaml_file}" And user updates the state "${new_state_yaml_file}" with "desiredState.workloads.backend.runtimeConfig.commandOptions=['-p', '8084:80']" - And user triggers "ank -k --no-wait set state desiredState.workloads.backend ${new_state_yaml_file}" + And user triggers "ank --no-wait set state desiredState.workloads.backend ${new_state_yaml_file}" And the workload "backend" shall have the execution state "Stopping(WaitingToStop)" on agent "agent_A" within "20" seconds - And user triggers "ank -k --no-wait delete workload frontend" + And user triggers "ank --no-wait delete workload frontend" # Asserts Then the workload "frontend" shall be removed and not exist on agent "agent_A" within "20" seconds And the workload "backend" shall have the execution state "Running(Ok)" on agent "agent_A" within "20" seconds @@ -114,9 +114,9 @@ Test Ankaios CLI update workload with pending create And Ankaios agent is started with name "agent_A" And the workload "after_backend" shall have the execution state "Succeeded(Ok)" on agent "agent_A" within "20" seconds # Actions - When user triggers "ank -k --no-wait set state desiredState.workloads.after_backend ${new_state_yaml_file}" + When user triggers "ank --no-wait set state desiredState.workloads.after_backend ${new_state_yaml_file}" And the workload "after_backend" shall have the execution state "Pending(WaitingToStart)" on agent "agent_A" within "3" seconds - And user triggers "ank -k set state desiredState.workloads.backend ${new_state_yaml_file}" + And user triggers "ank set state desiredState.workloads.backend ${new_state_yaml_file}" # Asserts Then the workload "backend" shall have the execution state "Succeeded(Ok)" on agent "agent_A" within "5" seconds And the workload "after_backend" shall have the execution state "Succeeded(Ok)" on agent "agent_A" within "5" seconds diff --git a/tests/stests/workloads/list_workloads.robot b/tests/stests/workloads/list_workloads.robot index 18a64b1af..064b87bb3 100644 --- a/tests/stests/workloads/list_workloads.robot +++ b/tests/stests/workloads/list_workloads.robot @@ -33,7 +33,7 @@ Test Ankaios CLI get workloads And the workload "hello2" shall have the execution state "Succeeded(Ok)" on agent "agent_B" And the workload "hello3" shall have the execution state "Succeeded(Ok)" on agent "agent_B" # Actions - When user triggers "ank -k get workloads" + When user triggers "ank get workloads" # Asserts Then in the last result, the workload "sleepy" shall have the execution state "Running(Ok)" on agent "agent_A" And in the last result, the workload "hello1" shall have the execution state "Failed(Lost)" on agent "agent_B" diff --git a/tests/stests/workloads/reject_state_with_cyclic_dependencies.robot b/tests/stests/workloads/reject_state_with_cyclic_dependencies.robot index fdd5c7767..dc6122d89 100644 --- a/tests/stests/workloads/reject_state_with_cyclic_dependencies.robot +++ b/tests/stests/workloads/reject_state_with_cyclic_dependencies.robot @@ -60,7 +60,7 @@ Test Ankaios CLI update state with cycle in interworkload dependencies is reject And Ankaios agent is started with name "agent_A" And the workload "workload_D" shall have the execution state "Succeeded(Ok)" on agent "agent_A" # Actions - And user triggers "ank -k set state ${new_state_yaml_file} desiredState.workloads.workload_C" + And user triggers "ank set state ${new_state_yaml_file} desiredState.workloads.workload_C" # Asserts Then the workload "workload_C" shall not exist And podman shall not have a container for workload "workload_C" on agent "agentA" within "5" seconds diff --git a/tests/stests/workloads/restart_handling_containerd.robot b/tests/stests/workloads/restart_handling_containerd.robot index ac60e6d05..e238f5079 100644 --- a/tests/stests/workloads/restart_handling_containerd.robot +++ b/tests/stests/workloads/restart_handling_containerd.robot @@ -20,12 +20,6 @@ Documentation Tests to verify that Ankaios restarts workloads according to Resource ../../resources/ankaios.resource Resource ../../resources/variables.resource - -*** Variables *** -${default_state_yaml_file} ${EMPTY} -${new_state_yaml_file} ${EMPTY} - - *** Test Cases *** # [stest->swdd~workload-control-loop-restarts-workload-with-enabled-restart-policy~2] # [stest->swdd~workload-control-loop-reuses-bundle-on-successful-restart~1] diff --git a/tests/stests/workloads/restart_handling_podman.robot b/tests/stests/workloads/restart_handling_podman.robot index 4013b6182..7daf6b4da 100644 --- a/tests/stests/workloads/restart_handling_podman.robot +++ b/tests/stests/workloads/restart_handling_podman.robot @@ -20,12 +20,6 @@ Documentation Tests to verify that Ankaios restarts workloads according to Resource ../../resources/ankaios.resource Resource ../../resources/variables.resource - -*** Variables *** -${default_state_yaml_file} ${EMPTY} -${new_state_yaml_file} ${EMPTY} - - *** Test Cases *** # [stest->swdd~workload-control-loop-restarts-workload-with-enabled-restart-policy~2] # [stest->swdd~workload-control-loop-reuses-bundle-on-successful-restart~1] diff --git a/tests/stests/workloads/restart_handling_podman_kube.robot b/tests/stests/workloads/restart_handling_podman_kube.robot index ae1e79b04..1e7649e62 100644 --- a/tests/stests/workloads/restart_handling_podman_kube.robot +++ b/tests/stests/workloads/restart_handling_podman_kube.robot @@ -20,12 +20,6 @@ Documentation Tests to verify that Ankaios restarts workloads according to Resource ../../resources/ankaios.resource Resource ../../resources/variables.resource - -*** Variables *** -${default_state_yaml_file} ${EMPTY} -${new_state_yaml_file} ${EMPTY} - - *** Test Cases *** # [stest->swdd~workload-control-loop-restarts-workload-with-enabled-restart-policy~2] Test Ankaios restarts podman kube workloads with restart policy ALWAYS. diff --git a/tests/stests/workloads/retry_creation_of_workload_containerd.robot b/tests/stests/workloads/retry_creation_of_workload_containerd.robot index 7c02fb7dd..57cbbc08f 100644 --- a/tests/stests/workloads/retry_creation_of_workload_containerd.robot +++ b/tests/stests/workloads/retry_creation_of_workload_containerd.robot @@ -50,10 +50,10 @@ Test Ankaios containerd retry creation of a workload on creation failure interce And Ankaios agent is started with name "agent_A" And the workload state of workload "invalid_image_workload" shall contain an additional info signaling retries within "5" seconds # Actions - When user triggers "ank -k get state > ${new_state_yaml_file}" - And user triggers "ank -k set state ${new_state_yaml_file} desiredState.workloads.invalid_image_workload" + When user triggers "ank get state > ${new_state_yaml_file}" + And user triggers "ank set state ${new_state_yaml_file} desiredState.workloads.invalid_image_workload" And user updates the state "${new_state_yaml_file}" with "desiredState.workloads.invalid_image_workload.runtimeConfig.image=ghcr.io/eclipse-ankaios/tests/alpine:latest" - And user triggers "ank -k set state desiredState.workloads.invalid_image_workload ${new_state_yaml_file}" + And user triggers "ank set state desiredState.workloads.invalid_image_workload ${new_state_yaml_file}" # Asserts Then the workload "invalid_image_workload" shall have the execution state "Succeeded(Ok)" from agent "agent_A" within "20" seconds [Teardown] Clean up Ankaios @@ -72,7 +72,7 @@ Test Ankaios containerd retry creation of a workload on creation failure interce And Ankaios agent is started with name "agent_A" And the workload state of workload "invalid_image_workload" shall contain an additional info signaling retries within "5" seconds # Actions - When user triggers "ank -k --no-wait delete workload invalid_image_workload" + When user triggers "ank --no-wait delete workload invalid_image_workload" # Asserts Then the workload "invalid_image_workload" shall be removed and not exist on agent "agent_A" within "20" seconds And containerd shall not have a container for workload "invalid_image_workload" on agent "agent_A" within "20" seconds diff --git a/tests/stests/workloads/retry_creation_of_workload_podman.robot b/tests/stests/workloads/retry_creation_of_workload_podman.robot index b71697e0f..cac5885c0 100644 --- a/tests/stests/workloads/retry_creation_of_workload_podman.robot +++ b/tests/stests/workloads/retry_creation_of_workload_podman.robot @@ -50,10 +50,10 @@ Test Ankaios Podman retry creation of a workload on creation failure intercepted And Ankaios agent is started with name "agent_A" And the workload state of workload "hello1" shall contain an additional info signaling retries within "5" seconds # Actions - When user triggers "ank -k get state > ${new_state_yaml_file}" - And user triggers "ank -k set state ${new_state_yaml_file} desiredState.workloads.hello1" + When user triggers "ank get state > ${new_state_yaml_file}" + And user triggers "ank set state ${new_state_yaml_file} desiredState.workloads.hello1" And user updates the state "${new_state_yaml_file}" with "desiredState.workloads.hello1.runtimeConfig.image=ghcr.io/eclipse-ankaios/tests/alpine:latest" - And user triggers "ank -k set state desiredState.workloads.hello1 ${new_state_yaml_file}" + And user triggers "ank set state desiredState.workloads.hello1 ${new_state_yaml_file}" # Asserts Then the workload "hello1" shall have the execution state "Succeeded(Ok)" from agent "agent_A" within "20" seconds [Teardown] Clean up Ankaios @@ -73,7 +73,7 @@ Test Ankaios Podman retry creation of a workload on creation failure intercepted And Ankaios agent is started with name "agent_A" And the workload state of workload "hello1" shall contain an additional info signaling retries within "5" seconds # Actions - When user triggers "ank -k --no-wait delete workload hello1" + When user triggers "ank --no-wait delete workload hello1" # Asserts Then the workload "hello1" shall be removed and not exist on agent "agent_A" within "20" seconds [Teardown] Clean up Ankaios diff --git a/tests/stests/workloads/state_operations_with_field_mask.robot b/tests/stests/workloads/state_operations_with_field_mask.robot index 1a1962091..1b94e5c48 100644 --- a/tests/stests/workloads/state_operations_with_field_mask.robot +++ b/tests/stests/workloads/state_operations_with_field_mask.robot @@ -40,7 +40,7 @@ Test Ankaios CLI update workload And Ankaios agent is started with name "agent_A" And the workload "simple" shall have the execution state "Succeeded(Ok)" on agent "agent_A" # Actions - And user triggers "ank -k --no-wait set state desiredState.workloads.simple.agent ${new_state_yaml_file}" + And user triggers "ank --no-wait set state desiredState.workloads.simple.agent ${new_state_yaml_file}" # Asserts Then the workload "simple" shall have the execution state "Removed" on agent "agent_A" within "20" seconds And podman shall not have a container for workload "simple" on agent "agent_A" @@ -58,7 +58,7 @@ Test Ankaios CLI get workloads with wildcard And Ankaios agent is started with name "agent_A" And the workload "sleepy" shall have the execution state "Running(Ok)" on agent "agent_A" # Actions - And user triggers "ank -k get state -o json 'desiredState.workloads.*.agent' 'desiredState.workloads.*.runtime'" + And user triggers "ank get state -o json 'desiredState.workloads.*.agent' 'desiredState.workloads.*.runtime'" # Asserts Then the last command shall contain the workload "sleepy" And the last command shall contain the workload "hello1" diff --git a/tests/stests/workloads/update_workload_containerd.robot b/tests/stests/workloads/update_workload_containerd.robot index 40b1bc149..2ccd972fe 100644 --- a/tests/stests/workloads/update_workload_containerd.robot +++ b/tests/stests/workloads/update_workload_containerd.robot @@ -43,9 +43,9 @@ Test Ankaios CLI update workload for containerd runtime And the workload "nginx" shall have the execution state "Running(Ok)" on agent "agent_A" within "10" seconds And the command "curl localhost:8081" finished with exit code "0" within "10" seconds # Actions - When user triggers "ank -k get state > ${new_state_file}" + When user triggers "ank get state > ${new_state_file}" And user updates the state "${new_state_file}" with "desiredState.workloads.nginx.runtimeConfig.commandOptions=['-p', '8082:80']" - And user triggers "ank -k set state desiredState.workloads.nginx ${new_state_file}" + And user triggers "ank set state desiredState.workloads.nginx ${new_state_file}" # Asserts Then the workload "nginx" shall have the execution state "Running(Ok)" on agent "agent_A" within "20" seconds And the command "curl localhost:8082" shall finish with exit code "0" within "10" seconds diff --git a/tests/stests/workloads/update_workload_podman.robot b/tests/stests/workloads/update_workload_podman.robot index 182b00bac..2b16542de 100644 --- a/tests/stests/workloads/update_workload_podman.robot +++ b/tests/stests/workloads/update_workload_podman.robot @@ -42,9 +42,9 @@ Test Ankaios CLI update workload And the workload "nginx" shall have the execution state "Running(Ok)" on agent "agent_A" And the command "curl localhost:8081" finished with exit code "0" # Actions - When user triggers "ank -k get state > ${new_state_yaml_file}" + When user triggers "ank get state > ${new_state_yaml_file}" And user updates the state "${new_state_yaml_file}" with "desiredState.workloads.nginx.runtimeConfig.commandOptions=['-p', '8082:80']" - And user triggers "ank -k set state desiredState.workloads.nginx ${new_state_yaml_file}" + And user triggers "ank set state desiredState.workloads.nginx ${new_state_yaml_file}" # Asserts Then the workload "nginx" shall have the execution state "Running(Ok)" on agent "agent_A" within "20" seconds And the command "curl localhost:8082" shall finish with exit code "0" within "10" seconds @@ -61,9 +61,9 @@ Test Ankaios Podman update workload from empty state And the CLI listens for workload states And Ankaios agent is started with name "agent_A" # Actions - When user triggers "ank -k get workloads" + When user triggers "ank get workloads" Then list of workloads shall be empty - When user triggers "ank -k set state desiredState.workloads ${CONFIGS_DIR}/update_state_create_one_workload.yaml" + When user triggers "ank set state desiredState.workloads ${CONFIGS_DIR}/update_state_create_one_workload.yaml" Then the workload "sleepy" shall have the execution state "Running(Ok)" on agent "agent_A" within "20" seconds [Teardown] Clean up Ankaios @@ -77,10 +77,10 @@ Test Ankaios Podman Update workload with invalid api version And Ankaios server is started without manifest successfully And Ankaios agent is started with name "agent_A" # Actions - When user triggers "ank -k get workloads" + When user triggers "ank get workloads" Then list of workloads shall be empty - When user triggers "ank -k set state desiredState ${CONFIGS_DIR}/update_state_invalid_version.yaml" - And user triggers "ank -k get workloads" + When user triggers "ank set state desiredState ${CONFIGS_DIR}/update_state_invalid_version.yaml" + And user triggers "ank get workloads" Then list of workloads shall be empty [Teardown] Clean up Ankaios @@ -96,10 +96,10 @@ Test Ankaios Podman Update workload with invalid workload name And Ankaios server is started without manifest successfully And Ankaios agent is started with name "agent_A" # Actions - When user triggers "ank -k get workloads" + When user triggers "ank get workloads" Then list of workloads shall be empty - When user triggers "ank -k set state desiredState ${CONFIGS_DIR}/update_state_invalid_names.yaml" - And user triggers "ank -k get workloads" + When user triggers "ank set state desiredState ${CONFIGS_DIR}/update_state_invalid_names.yaml" + And user triggers "ank get workloads" Then list of workloads shall be empty [Teardown] Clean up Ankaios @@ -115,10 +115,10 @@ Test Ankaios Podman Update workload with lengthy workload name And Ankaios server is started without manifest successfully And Ankaios agent is started with name "agent_A" # Actions - When user triggers "ank -k get workloads" + When user triggers "ank get workloads" Then list of workloads shall be empty - When user triggers "ank -k set state desiredState ${CONFIGS_DIR}/update_state_long_names.yaml" - And user triggers "ank -k get workloads" + When user triggers "ank set state desiredState ${CONFIGS_DIR}/update_state_long_names.yaml" + And user triggers "ank get workloads" Then list of workloads shall be empty [Teardown] Clean up Ankaios @@ -135,11 +135,11 @@ Test Ankaios Podman Update workload with invalid agent name And Ankaios server is started without manifest successfully And Ankaios agent is started with name "agent_A" # Actions - When user triggers "ank -k get workloads" + When user triggers "ank get workloads" Then list of workloads shall be empty - When user triggers "ank -k set state desiredState.workloads.sleepy ${CONFIGS_DIR}/update_state_invalid_names.yaml" + When user triggers "ank set state desiredState.workloads.sleepy ${CONFIGS_DIR}/update_state_invalid_names.yaml" And the last command finished with an error - And user triggers "ank -k get workloads" + And user triggers "ank get workloads" Then list of workloads shall be empty [Teardown] Clean up Ankaios @@ -156,7 +156,7 @@ Test Ankaios Podman Update workload support empty agent name in workload specifi And Ankaios server is started without manifest successfully And Ankaios agent is started with name "agent_A" # Actions - When user triggers "ank -k set state desiredState.workloads ${CONFIGS_DIR}/update_state_empty_agent_name.yaml" + When user triggers "ank set state desiredState.workloads ${CONFIGS_DIR}/update_state_empty_agent_name.yaml" And the last command finished with exit code "0" [Teardown] Clean up Ankaios @@ -171,10 +171,10 @@ Test Ankaios Podman Update workload with missing api version And Ankaios server is started without manifest successfully And Ankaios agent is started with name "agent_A" # Actions - When user triggers "ank -k get workloads" + When user triggers "ank get workloads" Then list of workloads shall be empty - When user triggers "ank -k set state desiredState ${CONFIGS_DIR}/update_state_missing_version.yaml" - And user triggers "ank -k get workloads" + When user triggers "ank set state desiredState ${CONFIGS_DIR}/update_state_missing_version.yaml" + And user triggers "ank get workloads" Then list of workloads shall be empty [Teardown] Clean up Ankaios diff --git a/tests/stests/workloads/workload_configs_podman.robot b/tests/stests/workloads/workload_configs_podman.robot index 567952a27..0f86b60ca 100644 --- a/tests/stests/workloads/workload_configs_podman.robot +++ b/tests/stests/workloads/workload_configs_podman.robot @@ -41,7 +41,7 @@ Test Ankaios start up with templated Ankaios manifest and update state with upda And the workload "nginx" shall have the execution state "Running(Ok)" on agent "agent_A" And the command "curl localhost:8081" finished with exit code "0" # Actions - When user triggers "ank -k set state desiredState.configs ${new_state_yaml_file}" + When user triggers "ank set state desiredState.configs ${new_state_yaml_file}" # Asserts Then the workload "nginx" shall have the execution state "Running(Ok)" on agent "agent_A" within "20" seconds And the command "curl localhost:8082" shall finish with exit code "0" within "10" seconds @@ -59,7 +59,7 @@ Test Ankaios update configs with invalid config item key And Ankaios agent is started with name "agent_A" # Actions Then the configs field inside the state shall be empty - When user triggers "ank -k set state desiredState.configs ${CONFIGS_DIR}/update_state_invalid_config_item_key.yaml" + When user triggers "ank set state desiredState.configs ${CONFIGS_DIR}/update_state_invalid_config_item_key.yaml" Then the configs field inside the state shall be empty [Teardown] Clean up Ankaios @@ -76,7 +76,7 @@ Test Ankaios update workload with invalid config alias And Ankaios agent is started with name "agent_A" # Actions Then the configs field inside the state shall be empty - When user triggers "ank -k set state desiredState ${CONFIGS_DIR}/update_state_invalid_workload_config_alias.yaml" + When user triggers "ank set state desiredState ${CONFIGS_DIR}/update_state_invalid_workload_config_alias.yaml" Then the configs field inside the state shall be empty [Teardown] Clean up Ankaios @@ -93,7 +93,7 @@ Test Ankaios update workload with invalid config reference key And Ankaios agent is started with name "agent_A" # Actions Then the configs field inside the state shall be empty - When user triggers "ank -k set state desiredState ${CONFIGS_DIR}/update_state_invalid_workload_config_reference_key.yaml" + When user triggers "ank set state desiredState ${CONFIGS_DIR}/update_state_invalid_workload_config_reference_key.yaml" Then the configs field inside the state shall be empty [Teardown] Clean up Ankaios diff --git a/tests/stests/workloads/workload_configs_podman_kube.robot b/tests/stests/workloads/workload_configs_podman_kube.robot index 07c348f96..608475903 100644 --- a/tests/stests/workloads/workload_configs_podman_kube.robot +++ b/tests/stests/workloads/workload_configs_podman_kube.robot @@ -19,11 +19,6 @@ Documentation Test of different cases related to workloads and their rende Resource ../../resources/ankaios.resource Resource ../../resources/variables.resource - -*** Variables *** -${start_up_yaml_file} ${EMPTY} - - *** Test Cases *** # [stest->swdd~config-renderer-supports-rendering-with-keeping-line-indent~1] Test Ankaios start up with templated Ankaios manifest keeping indentation level of rendered multi-line config diff --git a/tests/stests/workloads/workload_files.robot b/tests/stests/workloads/workload_files.robot index 497c76667..e52fb3116 100644 --- a/tests/stests/workloads/workload_files.robot +++ b/tests/stests/workloads/workload_files.robot @@ -59,7 +59,7 @@ Test Ankaios updates a workload upon update of its workload file content And the CLI listens for workload states And Ankaios agent is started with name "agent_A" And the workload "podman_workload_with_mounted_text_file" shall have the execution state "Running(Ok)" on agent "agent_A" - And user triggers "ank -k --no-wait set state desiredState.workloads.podman_workload_with_mounted_text_file desiredState.configs.web_server_config ${CONFIGS_DIR}/update_state_workload_files.yaml" + And user triggers "ank --no-wait set state desiredState.workloads.podman_workload_with_mounted_text_file desiredState.configs.web_server_config ${CONFIGS_DIR}/update_state_workload_files.yaml" # Asserts Then the workload "podman_workload_with_mounted_text_file" shall have the execution state "Running(Ok)" on agent "agent_A" And the command "curl -Lf localhost:8087/update" shall finish with exit code "0" @@ -80,10 +80,10 @@ Test Ankaios updates a workload upon adding additional workload files And Ankaios agent is started with name "agent_A" And the workload "podman_workload_with_mounted_binary_file" shall have the execution state "Succeeded(Ok)" on agent "agent_A" # First update the files only by setting the update mask - And user triggers "ank -k --no-wait set state desiredState.workloads.podman_workload_with_mounted_binary_file.files ${CONFIGS_DIR}/update_state_workload_files.yaml" + And user triggers "ank --no-wait set state desiredState.workloads.podman_workload_with_mounted_binary_file.files ${CONFIGS_DIR}/update_state_workload_files.yaml" And the workload "podman_workload_with_mounted_binary_file" shall have the execution state "Succeeded(Ok)" on agent "agent_A" # Now update the runtimeConfig calling the newly added workload file - And user triggers "ank -k --no-wait set state desiredState.workloads.podman_workload_with_mounted_binary_file.runtimeConfig ${CONFIGS_DIR}/update_state_workload_files.yaml" + And user triggers "ank --no-wait set state desiredState.workloads.podman_workload_with_mounted_binary_file.runtimeConfig ${CONFIGS_DIR}/update_state_workload_files.yaml" # Asserts Then the workload "podman_workload_with_mounted_binary_file" shall have the execution state "Succeeded(Ok)" on agent "agent_A" [Teardown] Clean up Ankaios From 6cb5c496cc1ecf2b3c2eca219121aa5e41ee8677 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 24 Sep 2026 09:59:19 +0200 Subject: [PATCH 19/55] Include unix sockets in documentation --- doc/docs/reference/config-files.md | 79 +++++++++---- doc/docs/usage/installation.md | 29 +---- .../usage/{mtls-setup.md => network-setup.md} | 107 +++++++++++++----- doc/docs/usage/shell-completion.md | 5 +- doc/docs/usage/tutorial-vehicle-signals.md | 26 +---- 5 files changed, 146 insertions(+), 100 deletions(-) rename doc/docs/usage/{mtls-setup.md => network-setup.md} (51%) diff --git a/doc/docs/reference/config-files.md b/doc/docs/reference/config-files.md index a48824caa..af1620ab1 100644 --- a/doc/docs/reference/config-files.md +++ b/doc/docs/reference/config-files.md @@ -18,7 +18,11 @@ The Ankaios configuration files are per default loaded from the following locati ## Configuration File Structure -The following three examples show how the Ankaios configuration files look like: +The following three examples show how the Ankaios configuration files look like. By default, the +standard [installation](../usage/installation.md) configures the server, agent and CLI to +communicate over a local Unix domain socket at `/run/ankaios/server.sock`, as shown in the examples +below. For communication over the network instead, see +[Connecting Ankaios components over the network](../usage/network-setup.md). ### Ankaios Server Configuration (`ank-server.conf`) @@ -33,22 +37,30 @@ version = 'v1' # By default, no startup configuration manifest is used. startup_manifest = '/etc/ankaios/state.yaml' -# The address, including the port, to which the server should listen. -address = '127.0.0.1:25551' +# The address to which the server should listen, either a TCP socket +# address including the port (`:`) or a Unix domain socket +# address (`unix://`). +address = 'unix:///run/ankaios/server.sock' + +# Optional group applied to the Unix domain socket file's group ownership. +# This option is only valid with 'unix://' addresses. +socket_group = 'ankaios' # The flag to disable TLS communication between # the Ankaios server, agents and the ank CLI. # If set to 'true' and the certificates are not provided, then the server shall not use TLS. -insecure = false +# This option must not be used with 'unix://' addresses. +# insecure = true # The path to ca certificate pem file. -ca_pem = '/etc/ankaios/certs/ca.pem' +# TLS options are only valid with TCP addresses. +# ca_pem = '/etc/ankaios/certs/ca.pem' # The path to server certificate pem file. -crt_pem = '/etc/ankaios/certs/ank-server.pem' +# crt_pem = '/etc/ankaios/certs/ank-server.pem' # The path to server key pem file. -key_pem = '/etc/ankaios/certs/ank-server-key.pem' +# key_pem = '/etc/ankaios/certs/ank-server-key.pem' # The content of the ca certificate pem file. # You can either provide key_pem or key_pem_content, but not both. @@ -69,6 +81,11 @@ key_pem = '/etc/ankaios/certs/ank-server-key.pem' # -----END PRIVATE KEY-----''' ``` +!!! note + + If no `address` is configured and no configuration file is used, the Ankaios server defaults to + the TCP address `127.0.0.1:25551`. + ### Ankaios Agent Configuration (`ank-agent.toml`) ```toml @@ -84,27 +101,31 @@ version = 'v1' # (a-z and A-Z), numbers and the symbols "-" and "_". name = 'agent_1' -# The server URL. -server_url = 'https://127.0.0.1:25551' +# The server address, either a TCP address (`https://:`) or a +# Unix domain socket address (`unix://`). +# The legacy field name 'server_url' is still accepted as an alias. +address = 'unix:///run/ankaios/server.sock' # An existing path where to manage the fifo files. # If not set, defaults to '$TMPDIR/ankaios/' (falls back to '/tmp/ankaios/' if TMPDIR is not set). # If the path already exists, it must be owned by the user running the agent and must not be # accessible by other users or groups; otherwise, the agent prevents startup to protect against insecure permissions. -run_folder = '/tmp/ankaios/' +# run_folder = '/tmp/ankaios/' # The flag to disable TLS communication with the server. # If set to 'true', then the agent shall not use TLS. -insecure = false +# This option must not be used with 'unix://' addresses. +# insecure = true # The path to the ca certificate pem file. -ca_pem = '/etc/ankaios/certs/ca.pem' +# TLS options are only valid with TCP addresses. +# ca_pem = '/etc/ankaios/certs/ca.pem' # The path to agent certificate pem file. -crt_pem = '/etc/ankaios/certs/ank-agent.pem' +# crt_pem = '/etc/ankaios/certs/ank-agent.pem' # The path to agent key pem file. -key_pem = '/etc/ankaios/certs/ank-agent-key.pem' +# key_pem = '/etc/ankaios/certs/ank-agent-key.pem' # The content of the ca certificate pem file. # You can either provide ca_pem or ca_pem_content, but not both @@ -125,6 +146,11 @@ key_pem = '/etc/ankaios/certs/ank-agent-key.pem' # -----END PRIVATE KEY-----''' ``` +!!! note + + If no `address` is configured and no configuration file is used, the Ankaios agent defaults to + the TCP address `https://127.0.0.1:25551`. + ### Ankaios CLI Configuration (`ank.conf`) ```toml @@ -135,35 +161,40 @@ key_pem = '/etc/ankaios/certs/ank-agent-key.pem' version = 'v1' # The timeout in milliseconds to wait for a response from the ank-server. -response_timeout = 3000 # milliseconds +# response_timeout = 3000 # milliseconds # The flag to enable verbose output. -verbose = false +# verbose = false # The flag to disable all output. -quiet = false +# quiet = false # The flag that enables waiting for workloads to be created/deleted. -no_wait = false +# no_wait = false [default] -# The URL to Ankaios server. +# The address of the Ankaios server, either a TCP address +# (`https://:`) or a Unix domain socket address +# (`unix://`). # If started in insecure mode then the HTTP protocol shall be used, # otherwise the HTTPS protocol shall be used. -server_url = 'https://127.0.0.1:25551' +# The legacy field name 'server_url' is still accepted as an alias. +address = 'unix:///run/ankaios/server.sock' # The flag to disable TLS communication with the server. # If set to 'true', then the CLI shall not use TLS. -insecure = false +# This option must not be used with 'unix://' addresses. +# insecure = true # The path to the ca certificate pem file. -ca_pem = '/etc/ankaios/certs/ca.pem' +# TLS options are only valid with TCP addresses. +# ca_pem = '/etc/ankaios/certs/ca.pem' # The path to CLI certificate pem file. -crt_pem = '/home/ankaios/.config/ankaios/ank.pem' +# crt_pem = '/home/ankaios/.config/ankaios/ank.pem' # The path to CLI key pem file. -key_pem = '/home/ankaios/.config/ankaios/ank-key.pem' +# key_pem = '/home/ankaios/.config/ankaios/ank-key.pem' # The content of the ca certificate pem file. # You can either provide ca_pem or ca_pem_content, but not both. diff --git a/doc/docs/usage/installation.md b/doc/docs/usage/installation.md index 92f939fac..be5525c01 100644 --- a/doc/docs/usage/installation.md +++ b/doc/docs/usage/installation.md @@ -14,14 +14,6 @@ Ankaios works with most Linux distributions and has been tested with Ubuntu 22.0 Detailed installation steps, including instructions on setting up container runtimes, choosing an installation method, and installing specific Ankaios versions, are provided below. -!!! Warning - - The express installation does not enable any authentication for access to - the Ankaios server (see [Setting up Ankaios with mTLS](mtls-setup.md)). - Since the server is typically run as root (e.g. via `sudo systemctl`), - any client that can reach it could start workloads with root privileges. - For this reason, only use this setup for development purposes. - ## System requirements Ankaios currently requires a Linux OS and is available for x86_64 and arm64 @@ -55,7 +47,9 @@ Ankaios uses the `nerdctl` command-line interface (CLI) to manage containers wit ## Installation methods -For setting up Ankaios in a production environment with mutual transport layer security (mTLS), follow the [mTLS setup instructions](mtls-setup.md) after installing Ankaios. +By default, the server, agent and CLI communicate over a local Unix domain socket, which relies on filesystem permissions and only works when all components run on the same host. +This default is not intended for production use. +For communication over the network, follow the [network setup instructions](network-setup.md) after installing Ankaios. ### Install script @@ -158,15 +152,6 @@ The `ankaios` meta-package installs all components. Individual packages can be i | `ank-agent` | Ankaios agent | | `ank` | Ankaios CLI | -!!! Warning - - The APT package installation does not enable any authentication for access to - the Ankaios server (see [Setting up Ankaios with mTLS](mtls-setup.md)). - Since the `ank-server` and `ank-agent` systemd services are started - automatically after installation, any client that can reach the `ank-server` - could start workloads with root privileges. For this reason, only use this - setup for development purposes. - The packages are compatible with Ubuntu 22.04+, Debian 12+ and other distributions based on glibc 2.35 or later. #### Uninstall @@ -230,14 +215,6 @@ You can also install: `sudo systemctl enable --now ank-server ank-agent` -!!! Warning - - The AUR package installation does not enable any authentication for access to the Ankaios server - (see [Setting up Ankaios with mTLS](mtls-setup.md)). - After starting the `ank-server` and `ank-agent` systemd services, - any client that can reach the `ank-server` could start workloads with root privileges. - For this reason, only use this setup for development purposes. - #### Uninstall ```shell diff --git a/doc/docs/usage/mtls-setup.md b/doc/docs/usage/network-setup.md similarity index 51% rename from doc/docs/usage/mtls-setup.md rename to doc/docs/usage/network-setup.md index d9e5e4e67..144168412 100644 --- a/doc/docs/usage/mtls-setup.md +++ b/doc/docs/usage/network-setup.md @@ -1,17 +1,24 @@ -# Setting up Ankaios with mTLS +# Connecting Ankaios components over the network - Mutual TLS (mTLS) is a security protocol that verifies both the client and server identities before establishing a connection. In Ankaios mTLS can be used to secure communication between the server, agent and ank CLI. + The standard [installation](installation.md) configures the server, agent and CLI to communicate over a local Unix domain socket (see the respective [configuration files](../reference/config-files.md)), which relies on filesystem permissions and only works when all components run on the same host. + To connect an agent or the CLI from a different host, or simply to reach the server over the network, the `address` in the server, agent and CLI configuration files needs to be switched from a `unix://` address to a network address (`:`). - The standard [installation](installation.md) **disables mTLS** for server, agent and CLI in the respective [configuration files](../reference/config-files.md) with `insecure = true`. - Besides that, mTLS can also be disabled for the CLI as command argument like `ank --insecure get state` or setting the environment variable `export ANK_INSECURE=true`. +!!! info - This page describes how to create certificates and **enable mTLS**. + For a quicker setup without certificates, intended only for isolated test environments such as VMs or containers set up for this purpose, see [Use network communication without TLS](#use-network-communication-without-tls) below. For anything else, secure the connection with mTLS as described next. -## Prerequisites +## Secure the connection with mTLS + + Mutual TLS (mTLS) is a security protocol that verifies both the client and server identities before establishing a connection. In Ankaios mTLS can be used to secure communication between the server, agent and ank CLI over the network. + The CLI can also connect over the network without mTLS; see [Use network communication without TLS](#use-network-communication-without-tls) below. + + This section describes how to switch to network communication, create certificates and **enable mTLS**. + +### Prerequisites - OpenSSL 3.0 or newer -## Set up directories +### Set up directories To set up mTLS with OpenSSL, perform the following actions: @@ -24,10 +31,10 @@ sudo mkdir -p /etc/ankaios/certs Then we need to create a folder to keep certificates and keys for the `ank` CLI: ```shell -mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/ankaios" +mkdir -p "$HOME/.config/ankaios" ``` -## Generate CA keys and certificate +### Generate CA keys and certificate Construct an [OpenSSL configuration file](https://www.openssl.org/docs/manmaster/man5/config.html) named `ca.cnf`. You are welcome to include additional fields if necessary: @@ -52,7 +59,7 @@ Generate CA certificate: sudo openssl req -config "./ca.cnf" -new -x509 -key "./ca-key.pem" -out "/etc/ankaios/certs/ca.pem" ``` -## Generate key and certificate for `ank-server` +### Generate key and certificate for `ank-server` Construct an [OpenSSL configuration file](https://www.openssl.org/docs/manmaster/man5/config.html) named `ank-server.cnf`. You are welcome to include additional fields if necessary: @@ -91,7 +98,7 @@ Generate ank-server certificate: sudo openssl x509 -req -in "./ank-server.csr" -CA "/etc/ankaios/certs/ca.pem" -CAkey "./ca-key.pem" -extensions v3_req -extfile "./ank-server.cnf" -out "/etc/ankaios/certs/ank-server.pem" ``` -## Generate key and certificate for `ank-agent` +### Generate key and certificate for `ank-agent` Construct an [OpenSSL configuration file](https://www.openssl.org/docs/manmaster/man5/config.html) named `ank-agent.cnf`. You are welcome to include additional fields if necessary: @@ -117,6 +124,10 @@ DNS.2 = agent_B ``` +!!! note + + This example shares one certificate between `agent_A` and `agent_B` to keep the tutorial short; for production use, issue a separate certificate per agent, each with a single `DNS.1` entry matching the agent's `--name`, so that an agent cannot connect under another agent's name. + Generate ank-agent key: ```shell @@ -135,7 +146,7 @@ Generate ank-agent certificate: sudo openssl x509 -req -in "./ank-agent.csr" -CA "/etc/ankaios/certs/ca.pem" -CAkey "./ca-key.pem" -extensions v3_req -extfile "./ank-agent.cnf" -out "/etc/ankaios/certs/ank-agent.pem" ``` -## Generate key and certificate for the CLI `ank` +### Generate key and certificate for the CLI `ank` Construct an [OpenSSL configuration file](https://www.openssl.org/docs/manmaster/man5/config.html) named `ank.cnf`. You are welcome to include additional fields if necessary: @@ -159,29 +170,35 @@ DNS.1 = ank Generate ank key: ```shell -openssl genpkey -algorithm ED25519 -out "${XDG_CONFIG_HOME:-$HOME/.config}/ankaios/ank-key.pem" +openssl genpkey -algorithm ED25519 -out "$HOME/.config/ankaios/ank-key.pem" ``` Generate ank certificate signing request: ```shell -openssl req -config "./ank.cnf" -new -key "${XDG_CONFIG_HOME:-$HOME/.config}/ankaios/ank-key.pem" -out "./ank.csr" +openssl req -config "./ank.cnf" -new -key "$HOME/.config/ankaios/ank-key.pem" -out "./ank.csr" ``` Generate ank certificate: ```shell -sudo openssl x509 -req -in "./ank.csr" -CA "/etc/ankaios/certs/ca.pem" -CAkey "./ca-key.pem" -extensions v3_req -extfile "./ank.cnf" -out "${XDG_CONFIG_HOME:-$HOME/.config}/ankaios/ank.pem" +sudo openssl x509 -req -in "./ank.csr" -CA "/etc/ankaios/certs/ca.pem" -CAkey "./ca-key.pem" -extensions v3_req -extfile "./ank.cnf" -out "$HOME/.config/ankaios/ank.pem" ``` -## Perform Ankaios installation with mTLS support +!!! warning -To set up Ankaios with mTLS support, you need to supply the necessary mTLS certificates to the `ank-server`, `ank-agent`, and `ank` CLI components. Here's a step-by-step guide: + All private key files (`*-key.pem`) should be readable only by their owner, e.g. with `chmod 600`. -### Install `ank-server` and `ank-agent` with mTLS certificates +### Switch to network communication with mTLS support -```shell -curl -sfL https://github.com/eclipse-ankaios/ankaios/releases/latest/download/install.sh | bash +To set up Ankaios with mTLS support, you need to supply the necessary mTLS certificates to the already installed `ank-server`, `ank-agent`, and `ank` CLI components. Here's a step-by-step guide: + +#### Configure `ank-server` and `ank-agent` with mTLS certificates + +Replace the default `address` in `/etc/ankaios/ank-server.conf` with a network address and remove `socket_group`, which only applies to Unix domain sockets: + +```toml +address = '0.0.0.0:25551' ``` To use the CA, certificate and key for the server, add the following lines to `/etc/ankaios/ank-server.conf` and set the `insecure` flag inside it to `false`: @@ -192,7 +209,14 @@ crt_pem = '/etc/ankaios/certs/ank-server.pem' key_pem = '/etc/ankaios/certs/ank-server-key.pem' ``` -For the agent add the following lines to `/etc/ankaios/ank-agent.conf` and set the `insecure` flag inside it to `false`: +Similarly, switch `/etc/ankaios/ank-agent.conf` to the network address of the server, using the `https://` scheme, and set `insecure` to `false`: + +```toml +address = 'https://127.0.0.1:25551' +insecure = false +``` + +For the agent add the following lines to `/etc/ankaios/ank-agent.conf`: ```toml ca_pem = '/etc/ankaios/certs/ca.pem' @@ -204,14 +228,14 @@ For more information on how the server, agent and CLI can be configured please c Start the Ankaios server and an Ankaios agent as described in the [Quickstart](quickstart.md) and continue below to configure the CLI with mTLS. -### Configure the `ank` CLI with mTLS certificates +#### Configure the `ank` CLI with mTLS certificates To make it easier, we will set the mTLS certificates for the `ank` CLI by using environment variables: ```shell export ANK_CA_PEM=/etc/ankaios/certs/ca.pem -export ANK_CRT_PEM=${XDG_CONFIG_HOME:-$HOME/.config}/ankaios/ank.pem -export ANK_KEY_PEM=${XDG_CONFIG_HOME:-$HOME/.config}/ankaios/ank-key.pem +export ANK_CRT_PEM=$HOME/.config/ankaios/ank.pem +export ANK_KEY_PEM=$HOME/.config/ankaios/ank-key.pem ``` Now you can use the `ank` CLI as follows: @@ -223,14 +247,43 @@ ank get workloads Or in a single line call: ```shell -ANK_CA_PEM=/etc/ankaios/certs/ca.pem ANK_CRT_PEM=${XDG_CONFIG_HOME:-$HOME/.config}/ankaios/ank.pem ANK_KEY_PEM=${XDG_CONFIG_HOME:-$HOME/.config}/ankaios/ank-key.pem ank get workloads +ANK_CA_PEM=/etc/ankaios/certs/ca.pem ANK_CRT_PEM=$HOME/.config/ankaios/ank.pem ANK_KEY_PEM=$HOME/.config/ankaios/ank-key.pem ank get workloads ``` Alternatively, you can pass the mTLS certificates as command line arguments: ```shell -ank --ca_pem=/etc/ankaios/certs/ca.pem --crt_pem="${XDG_CONFIG_HOME:-$HOME/.config}/ankaios/ank.pem" --key_pem="${XDG_CONFIG_HOME:-$HOME/.config}/ankaios/ank-key.pem" get workloads +ank --ca_pem=/etc/ankaios/certs/ca.pem --crt_pem="$HOME/.config/ankaios/ank.pem" --key_pem="$HOME/.config/ankaios/ank-key.pem" get workloads ``` Or you can also configure the mTLS certificates in the CLI configuration file `~/.config/ankaios/ank.conf`. -In any case you should set `insecure = false` in there to prevent a warning when mTLS certificates are provided. +In any case, make sure the `address` in there is set to the network address of the server (e.g. `https://127.0.0.1:25551`) and `insecure = false` is set to prevent a warning when mTLS certificates are provided. + +## Use network communication without TLS + +!!! warning + + Network communication without TLS is neither authenticated nor encrypted. Since `ank-server` typically runs as root (e.g. via `sudo systemctl`), **any program or user that can reach the port** - every local user on the machine, and every host on the network if the port is not otherwise firewalled - can act as an agent or CLI against the server and start workloads with root privileges. Only use network communication without TLS in isolated test environments such as VMs or containers set up for this purpose. For anything else, secure the connection with mTLS as described above. + +Replace the default `address` in `/etc/ankaios/ank-server.conf` and remove `socket_group`, which only applies to Unix domain sockets: + +```toml +address = '0.0.0.0:25551' +insecure = true +``` + +Replace the default `address` in `/etc/ankaios/ank-agent.conf` with the network address of the server and mark the connection as insecure: + +```toml +address = 'http://:25551' +insecure = true +``` + +Do the same in the CLI configuration file `~/.config/ankaios/ank.conf`, or instead pass `ank --insecure -s http://:25551 get state` or set `export ANK_INSECURE=true` and `export ANK_SERVER_URL=http://:25551`. + +Restart the server and agent to apply the new configuration: + +```shell +sudo systemctl restart ank-server +sudo systemctl restart ank-agent +``` diff --git a/doc/docs/usage/shell-completion.md b/doc/docs/usage/shell-completion.md index f659e9588..6a22dace8 100644 --- a/doc/docs/usage/shell-completion.md +++ b/doc/docs/usage/shell-completion.md @@ -4,9 +4,8 @@ Ankaios supports command completion for the `ank` CLI in various shells. !!! note - For dynamic completion (workloads etc.) to work, the `ank` CLI must be configured via environment variables. - To use a non-default server URL, provide `ANK_SERVER_URL`. - Also provide either `ANK_INSECURE=true` or `ANK_CA_PEM`, `ANK_CRT_PEM` and `ANK_KEY_PEM`. + Dynamic completion (workloads etc.) invokes the `ank` CLI internally and therefore requires the + `ank` CLI to be able to connect to the Ankaios server (see [configuration files](../reference/config-files.md)). ## Bash diff --git a/doc/docs/usage/tutorial-vehicle-signals.md b/doc/docs/usage/tutorial-vehicle-signals.md index 56f0a73ec..c652781c7 100644 --- a/doc/docs/usage/tutorial-vehicle-signals.md +++ b/doc/docs/usage/tutorial-vehicle-signals.md @@ -11,7 +11,6 @@ The central workload will be a databroker from the [Kuksa.val project](https://g To run this tutorial you will need a Linux platform, which can be a RaspberryPi or a Linux PC or virtual machine. -Additionally, it's assumed that the Ankaios setup is done with mutual TLS (mTLS) disabled or using its default installation settings. ## Start the databroker @@ -112,16 +111,15 @@ The next workload we want to start is a speed consumer that consumes vehicle spe A speed consumer such as a navigation system typically runs on a separate node for infotainment. A separate node requires a new Ankaios agent. Let's create another Ankaios agent to connect to the existing server. For this tutorial we can either use a separate Linux host or use the existing one. -Start a new agent with: -```shell -ank-agent --name infotainment --server-url http://:25551 -``` +!!! info + + For a genuine multi-node setup, i.e. running the new agent on a separate host from the server, read [Connecting Ankaios components over the network](network-setup.md) and follow it to set up the cluster with network communication, ideally secured with mTLS, then start an agent named `infotainment` on the separate host. Keep in mind that the agent's certificate needs to be generated for that name. -If the agent is started on the same host as the existing Ankaios server and agent, then we will call it as follows: +If you run the new agent on the same host as the existing Ankaios server and agent, no further configuration is needed, since the standard installation already configures agents to connect to the server over the local Unix domain socket. Simply start the new agent with: ```shell -ank-agent --name infotainment --server-url http://127.0.0.1:25551 +ank-agent --name infotainment ``` As the first agent was started by systemd, it runs as root and therefore calls podman as root. @@ -182,19 +180,7 @@ ank apply --agent infotainment speed-consumer.yaml !!! note - If you are running the ank command on a host that is different from the host - on which the Ankaios server is running, you need to add a parameter `-s ` like: - - ``` - ank apply -s http://127.0.0.1:25551 --agent infotainment speed-consumer.yaml - ``` - - Optionally the server URL can also be provided via environment variable: - - ``` - export ANK_SERVER_URL=http://127.0.0.1:25551 - ank apply --agent infotainment speed-consumer.yaml - ``` + If you are running the `ank` CLI on a host that is different from the host on which the Ankaios server is running, you need to configure it to connect over the network, as described in [Connecting Ankaios components over the network](network-setup.md). Once configured, you can start it as described above. The command waits until speed consumer is running. It should print: From 4558b59b4b00cb9f3dbb22bf57faaeedd0166768 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:12:50 +0200 Subject: [PATCH 20/55] Print error if ank.conf exists but is invalid --- agent/src/main.rs | 3 ++- ank/src/main.rs | 4 ++-- common/src/config.rs | 30 ++++++++++++++++++------------ server/src/main.rs | 3 ++- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/agent/src/main.rs b/agent/src/main.rs index a4745af5b..172bfdd52 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -121,7 +121,8 @@ async fn main() { // [impl->swdd~agent-loads-config-file~2] let mut agent_config: AgentConfig = - handle_config(&args.config_path, &DEFAULT_AGENT_CONFIG_FILE_PATH); + handle_config(&args.config_path, &DEFAULT_AGENT_CONFIG_FILE_PATH) + .unwrap_or_exit("Config file could not be parsed"); agent_config .update_with_args(&args) diff --git a/ank/src/main.rs b/ank/src/main.rs index 17b22afbc..e6a2d2686 100644 --- a/ank/src/main.rs +++ b/ank/src/main.rs @@ -37,8 +37,8 @@ async fn main() { // [impl->swdd~cli-loads-config-file~2] let config_file_paths = get_config_file_paths(); let config_file_paths_refs: Vec<&str> = config_file_paths.iter().map(|s| s.as_str()).collect(); - let mut ank_config: AnkConfig = - handle_config(&args.config_path, &config_file_paths_refs); + let mut ank_config: AnkConfig = handle_config(&args.config_path, &config_file_paths_refs) + .unwrap_or_exit_func(|err| output_and_error!("Config file could not be parsed: {}", err), -1); ank_config.update_with_args(&args) .unwrap_or_exit_func(|err| output_and_error!("{}", err), -1); diff --git a/common/src/config.rs b/common/src/config.rs index cf9643d62..48b6960ee 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -12,7 +12,6 @@ // // SPDX-License-Identifier: Apache-2.0 -use crate::std_extensions::GracefulExitResult; use std::fmt; use std::path::Path; use std::path::PathBuf; @@ -106,10 +105,16 @@ impl fmt::Display for ConversionErrors { /// existing configuration file. If no configuration file is found, it will return the /// default configuration. /// +/// Loading errors are returned to the caller instead of being handled here, so that each +/// component can report them through its own tracing/logging mechanism. +/// /// ## Returns /// -/// The loaded configuration. -pub fn handle_config(config_path: &Option, default_paths: &[&str]) -> T { +/// The loaded configuration or the error that occurred while loading it. +pub fn handle_config( + config_path: &Option, + default_paths: &[&str], +) -> Result { match config_path { Some(config_path) => { let config_path = PathBuf::from(config_path); @@ -117,7 +122,7 @@ pub fn handle_config(config_path: &Option, default_paths: "Loading config from user provided path '{}'", config_path.display() ); - T::from_file(config_path).unwrap_or_exit("Config file could not be parsed") + T::from_file(config_path) } None => { for path in default_paths { @@ -127,8 +132,7 @@ pub fn handle_config(config_path: &Option, default_paths: "Loading config from default path '{}'", default_path.display() ); - return T::from_file(default_path) - .unwrap_or_exit("Config file could not be parsed"); + return T::from_file(default_path); } } @@ -136,7 +140,7 @@ pub fn handle_config(config_path: &Option, default_paths: "No config file found at default paths '{:?}'. Continue with default config.", default_paths, ); - T::default() + Ok(T::default()) } } } @@ -281,7 +285,8 @@ mod tests { let test_config: TestConfig = handle_config( &Some(tmp_config.into_temp_path().to_str().unwrap().to_string()), &["/a/very/invalid/path/to/config/file"], - ); + ) + .unwrap(); assert_eq!(test_config.test_string, "test_value"); assert!(test_config.test_bool); @@ -292,7 +297,8 @@ mod tests { let mut file = NamedTempFile::new().expect("Failed to create file"); writeln!(file, "{VALID_TEST_CONFIG_CONTENT}").expect("Failed to write to file"); - let test_config: TestConfig = handle_config(&None, &[file.path().to_str().unwrap()]); + let test_config: TestConfig = + handle_config(&None, &[file.path().to_str().unwrap()]).unwrap(); assert_eq!(test_config.test_string, "test_value"); assert!(test_config.test_bool); @@ -314,7 +320,7 @@ mod tests { let file_path_1 = default_file_1.path().to_str().unwrap().to_owned(); let file_path_2 = default_file_2.path().to_str().unwrap().to_owned(); - let test_config: TestConfig = handle_config(&None, &[&file_path_1, &file_path_2]); + let test_config: TestConfig = handle_config(&None, &[&file_path_1, &file_path_2]).unwrap(); assert_eq!(test_config.test_string, "test_value"); assert!(test_config.test_bool); @@ -322,7 +328,7 @@ mod tests { // config file 1 is deleted, so config file 2 should be loaded drop(default_file_1); - let test_config: TestConfig = handle_config(&None, &[&file_path_1, &file_path_2]); + let test_config: TestConfig = handle_config(&None, &[&file_path_1, &file_path_2]).unwrap(); assert_eq!(test_config.test_string, CHANGED_TEST_VALUE); assert!(test_config.test_bool); @@ -331,7 +337,7 @@ mod tests { #[test] fn utest_handle_config_default() { let test_config: TestConfig = - handle_config(&None, &["/a/very/invalid/path/to/config/file"]); + handle_config(&None, &["/a/very/invalid/path/to/config/file"]).unwrap(); assert_eq!(test_config, TestConfig::default()); } diff --git a/server/src/main.rs b/server/src/main.rs index 304bb9b0f..8d0ef71f7 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -63,7 +63,8 @@ async fn main() { // [impl->swdd~server-loads-config-file~2] let mut server_config: ServerConfig = - handle_config(&args.config_path, &DEFAULT_SERVER_CONFIG_FILE_PATH); + handle_config(&args.config_path, &DEFAULT_SERVER_CONFIG_FILE_PATH) + .unwrap_or_exit("Config file could not be parsed"); server_config .update_with_args(&args) From 10446ff6a7e09c358107430569ff08925554e26e Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 24 Sep 2026 15:46:35 +0200 Subject: [PATCH 21/55] Add needed group for unix socket to AUR packages --- tools/aur/PKGBUILD-ankaios-bin.m4 | 5 ++++- tools/aur/PKGBUILD-ankaios-git.m4 | 5 ++++- tools/aur/PKGBUILD-ankaios.m4 | 5 ++++- tools/aur/README.md | 19 ++++++++++++++++--- tools/aur/ank-server.service | 5 ++++- tools/aur/publish_to_aur.sh | 11 +++-------- 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/tools/aur/PKGBUILD-ankaios-bin.m4 b/tools/aur/PKGBUILD-ankaios-bin.m4 index b25ca62dc..540222574 100644 --- a/tools/aur/PKGBUILD-ankaios-bin.m4 +++ b/tools/aur/PKGBUILD-ankaios-bin.m4 @@ -12,10 +12,12 @@ depends=('libgcc' 'glibc') source=("$pkgbase-${pkgver}_configs.tar.gz::https://github.com/eclipse-ankaios/ankaios/releases/download/v$pkgver/ankaios_configs.tar.gz" "$pkgbase-${pkgver}_man-pages.tar.gz::https://github.com/eclipse-ankaios/ankaios/releases/download/v$pkgver/man-pages.tar.gz" 'ank-server.service' - 'ank-agent.service') + 'ank-agent.service' + 'ankaios.sysusers') source_x86_64=("$pkgbase-$pkgver-x86_64.tar.gz::https://github.com/eclipse-ankaios/ankaios/releases/download/v$pkgver/ankaios-linux-amd64.tar.gz") source_aarch64=("$pkgbase-$pkgver-aarch64.tar.gz::https://github.com/eclipse-ankaios/ankaios/releases/download/v$pkgver/ankaios-linux-arm64.tar.gz") b2sums=('xxxxx' + 'xxxxx' 'xxxxx' 'xxxxx' 'xxxxx') @@ -31,6 +33,7 @@ package_ankaios-server-bin() { install -Dm755 -t "$pkgdir"/usr/bin/ "ank-server" install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ ank-server.service + install -Dm644 ankaios.sysusers "$pkgdir"/usr/lib/sysusers.d/ankaios.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ ank-server.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ state.yaml install -Dm644 -t "$pkgdir"/usr/share/man/man8 man8/ank-server.8 diff --git a/tools/aur/PKGBUILD-ankaios-git.m4 b/tools/aur/PKGBUILD-ankaios-git.m4 index c0eb722d0..2451b2a27 100644 --- a/tools/aur/PKGBUILD-ankaios-git.m4 +++ b/tools/aur/PKGBUILD-ankaios-git.m4 @@ -12,8 +12,10 @@ depends=('libgcc' 'glibc') makedepends=('cargo' 'git' 'protobuf' 'help2man') source=("$pkgbase::git+https://github.com/eclipse-ankaios/ankaios.git" 'ank-server.service' - 'ank-agent.service') + 'ank-agent.service' + 'ankaios.sysusers') b2sums=('SKIP' + 'xxxxx' 'xxxxx' 'xxxxx') @@ -46,6 +48,7 @@ package_ankaios-server-git() { install -Dm755 -t "$pkgdir"/usr/bin/ "$pkgbase/target/$(rustc --print host-tuple)/release/ank-server" install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ ank-server.service + install -Dm644 ankaios.sysusers "$pkgdir"/usr/lib/sysusers.d/ankaios.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ "$pkgbase"/server/config/ank-server.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ "$pkgbase"/server/config/state.yaml install -Dm644 -t "$pkgdir"/usr/share/man/man8 "$pkgbase"/build/man/man8/ank-server.8 diff --git a/tools/aur/PKGBUILD-ankaios.m4 b/tools/aur/PKGBUILD-ankaios.m4 index f9104a930..438f8cc31 100644 --- a/tools/aur/PKGBUILD-ankaios.m4 +++ b/tools/aur/PKGBUILD-ankaios.m4 @@ -12,8 +12,10 @@ depends=('libgcc' 'glibc') makedepends=('cargo' 'protobuf' 'help2man') source=("$pkgbase-$pkgver.tar.gz::https://github.com/eclipse-ankaios/ankaios/archive/refs/tags/v$pkgver.tar.gz" 'ank-server.service' - 'ank-agent.service') + 'ank-agent.service' + 'ankaios.sysusers') b2sums=('xxxxxx' + 'xxxxxx' 'xxxxxx' 'xxxxxx') @@ -39,6 +41,7 @@ package_ankaios-server() { install -Dm755 -t "$pkgdir"/usr/bin/ "ankaios-$pkgver/target/$(rustc --print host-tuple)/release/ank-server" install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ ank-server.service + install -Dm644 ankaios.sysusers "$pkgdir"/usr/lib/sysusers.d/ankaios.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ "ankaios-$pkgver"/server/config/ank-server.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ "ankaios-$pkgver"/server/config/state.yaml install -Dm644 -t "$pkgdir"/usr/share/man/man8 "ankaios-$pkgver"/build/man/man8/ank-server.8 diff --git a/tools/aur/README.md b/tools/aur/README.md index a72f88644..100f66543 100644 --- a/tools/aur/README.md +++ b/tools/aur/README.md @@ -13,16 +13,18 @@ They are intended to be run manually by the package maintainer outside the dev c | File | Purpose | | ------ | --------- | -| `publish_to_aur.sh` | Clones AUR repositories, generates `PKGBUILD` from templates, updates checksums and `.SRCINFO`, commits, and pushes changes | +| `build_package.sh` | Generates `PKGBUILD` from a template, resolves checksums, and generates `.SRCINFO` for a single package variant into a given output directory. Does not touch AUR | +| `publish_to_aur.sh` | Clones AUR repositories, calls `build_package.sh` for each package variant, commits, and pushes changes | | `PKGBUILD-ankaios.m4` | Template for the release-from-source AUR package | | `PKGBUILD-ankaios-bin.m4` | Template for the prebuilt-binary AUR package | | `PKGBUILD-ankaios-git.m4` | Template for the git-head AUR package | -| `ank-server.service`, `ank-agent.service`, `ankaios-cli.install` | Asset files copied into each AUR package repository | +| `ank-server.service`, `ank-agent.service`, `ankaios-cli.install`, `ankaios.sysusers` | Asset files copied into each AUR package repository | +| `ankaios.sysusers` | `systemd-sysusers` fragment creating the system group `ankaios` used for the server's Unix domain socket (`/run/ankaios/server.sock`), installed to `/usr/lib/sysusers.d/ankaios.conf` | ## Required environment - `ANKAIOS_VERSION` must be set (for example `1.0.0`) -- SSH access to AUR (`aur@aur.archlinux.org`) must be configured, including the correct SSH key for the AUR maintainer account being available and loaded +- SSH access to AUR (`aur@aur.archlinux.org`) must be configured, including the correct SSH key for the AUR maintainer account being available and loaded (only needed for `publish_to_aur.sh`) - Required tools must be available: `m4`, `updpkgsums`, `makepkg`, `git`. Install them with `sudo pacman -S m4 pacman-contrib git`. ## Manual invocation @@ -31,6 +33,17 @@ They are intended to be run manually by the package maintainer outside the dev c ANKAIOS_VERSION=1.0.0 ./publish_to_aur.sh ``` +## Building a package locally (no AUR access) + +Use `build_package.sh` directly to generate `PKGBUILD`/`.SRCINFO` and resolve checksums for a single package variant, without cloning from or pushing to AUR. +This does not require SSH access to AUR, but `updpkgsums` and `makepkg` still require an Arch (or Arch-compatible) environment: + +```bash +ANKAIOS_VERSION=v1.0.0 ./build_package.sh ankaios /tmp/ankaios-build +cd /tmp/ankaios-build +makepkg -s +``` + ## Useful links - [AUR submission guidelines](https://wiki.archlinux.org/title/AUR_submission_guidelines) diff --git a/tools/aur/ank-server.service b/tools/aur/ank-server.service index 914fbb871..10fa0a597 100644 --- a/tools/aur/ank-server.service +++ b/tools/aur/ank-server.service @@ -1,10 +1,13 @@ [Unit] Description=Ankaios server -After=network.target +After=network.target Wants=network.target [Service] Environment="RUST_LOG=info" +ExecStartPre=/usr/bin/mkdir -p /run/ankaios +ExecStartPre=/usr/bin/chgrp ankaios /run/ankaios +ExecStartPre=/usr/bin/chmod 0750 /run/ankaios ExecStart=/usr/bin/ank-server [Install] diff --git a/tools/aur/publish_to_aur.sh b/tools/aur/publish_to_aur.sh index ddbce4ad6..91c9a0950 100755 --- a/tools/aur/publish_to_aur.sh +++ b/tools/aur/publish_to_aur.sh @@ -3,7 +3,6 @@ set -e SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) AUR_REPO_BASE="${AUR_REPO_BASE:-ssh://aur@aur.archlinux.org}" -ASSETS=("ank-server.service" "ank-agent.service" "ankaios-cli.install") if [ -z "${ANKAIOS_VERSION}" ]; then echo "Error: ANKAIOS_VERSION environment variable is not set." @@ -14,7 +13,6 @@ tmp_dir="$(mktemp -d)" echo "$tmp_dir" trap 'rm -rf "$tmp_dir"' EXIT -cd "$SCRIPT_DIR" for package in ankaios ankaios-bin ankaios-git; do git clone "$AUR_REPO_BASE/$package.git" "$tmp_dir/$package" @@ -22,13 +20,10 @@ for package in ankaios ankaios-bin ankaios-git; do git checkout -b master || true # AUR only accepts the master branch rm -rf -- * - m4 -D ANKAIOS_VERSION="$ANKAIOS_VERSION" "$SCRIPT_DIR/PKGBUILD-$package.m4" > PKGBUILD - cp -t ./ "${ASSETS[@]/#/$SCRIPT_DIR/}" - updpkgsums - makepkg --printsrcinfo > .SRCINFO + "$SCRIPT_DIR/build_package.sh" "$package" "$tmp_dir/$package" - git add PKGBUILD .SRCINFO "${ASSETS[@]}" - git commit -a -m "Update version to $ANKAIOS_VERSION" || true + git add -A + git commit -m "Update version to $ANKAIOS_VERSION" || true git push cd "$SCRIPT_DIR" From b6c58badc041dee6f02c7d3beee93423590027a7 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:29:35 +0200 Subject: [PATCH 22/55] Fix address setup in system tests --- tests/resources/ankaios.resource | 54 ++++++++++++++----------- tests/resources/configs/ank-server.conf | 2 - tests/stests/configs/config_file.robot | 2 +- 3 files changed, 31 insertions(+), 27 deletions(-) delete mode 100644 tests/resources/configs/ank-server.conf diff --git a/tests/resources/ankaios.resource b/tests/resources/ankaios.resource index e667e2b6b..45f85eef7 100644 --- a/tests/resources/ankaios.resource +++ b/tests/resources/ankaios.resource @@ -51,7 +51,11 @@ Setup Ankaios without MTLS Setup Setup Ankaios [Arguments] ${mtls_enabled}=False - Setup MTLS enabled=${mtls_enabled} + IF ${mtls_enabled} == True + Setup mTLS + ELSE + Setup Unix Socket + END Setup Ankaios without MTLS Setup Setup Ankaios for mTLS with explicit certs @@ -105,29 +109,31 @@ Clean up containerd Log Clean up containerd containerd has deleted all existing containers -Setup MTLS - [Arguments] ${enabled} - IF ${enabled} == True - Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_TCP_ADDRESS} - Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} - Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} - ${ca_pem}= Catenate SEPARATOR= ${CERTS_DIR} /ca.pem - ${server_crt_pem}= Catenate SEPARATOR= ${CERTS_DIR} /server.pem - ${server_key_pem}= Catenate SEPARATOR= ${CERTS_DIR} /server-key.pem - ${agent_crt_pem}= Catenate SEPARATOR= ${CERTS_DIR} /agent.pem - ${agent_key_pem}= Catenate SEPARATOR= ${CERTS_DIR} /agent-key.pem - ${cli_crt_pem}= Catenate SEPARATOR= ${CERTS_DIR} /cli.pem - ${cli_key_pem}= Catenate SEPARATOR= ${CERTS_DIR} /cli-key.pem - Set Environment Variable name=ANKSERVER_CA_PEM value=${ca_pem} - Set Environment Variable name=ANKSERVER_CRT_PEM value=${server_crt_pem} - Set Environment Variable name=ANKSERVER_KEY_PEM value=${server_key_pem} - Set Environment Variable name=ANKAGENT_CA_PEM value=${ca_pem} - Set Environment Variable name=ANKAGENT_CRT_PEM value=${agent_crt_pem} - Set Environment Variable name=ANKAGENT_KEY_PEM value=${agent_key_pem} - Set Environment Variable name=ANK_CA_PEM value=${ca_pem} - Set Environment Variable name=ANK_CRT_PEM value=${cli_crt_pem} - Set Environment Variable name=ANK_KEY_PEM value=${cli_key_pem} - END +Setup mTLS + Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_TCP_ADDRESS} + Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} + Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_HTTPS_URL} + ${ca_pem}= Catenate SEPARATOR= ${CERTS_DIR} /ca.pem + ${server_crt_pem}= Catenate SEPARATOR= ${CERTS_DIR} /server.pem + ${server_key_pem}= Catenate SEPARATOR= ${CERTS_DIR} /server-key.pem + ${agent_crt_pem}= Catenate SEPARATOR= ${CERTS_DIR} /agent.pem + ${agent_key_pem}= Catenate SEPARATOR= ${CERTS_DIR} /agent-key.pem + ${cli_crt_pem}= Catenate SEPARATOR= ${CERTS_DIR} /cli.pem + ${cli_key_pem}= Catenate SEPARATOR= ${CERTS_DIR} /cli-key.pem + Set Environment Variable name=ANKSERVER_CA_PEM value=${ca_pem} + Set Environment Variable name=ANKSERVER_CRT_PEM value=${server_crt_pem} + Set Environment Variable name=ANKSERVER_KEY_PEM value=${server_key_pem} + Set Environment Variable name=ANKAGENT_CA_PEM value=${ca_pem} + Set Environment Variable name=ANKAGENT_CRT_PEM value=${agent_crt_pem} + Set Environment Variable name=ANKAGENT_KEY_PEM value=${agent_key_pem} + Set Environment Variable name=ANK_CA_PEM value=${ca_pem} + Set Environment Variable name=ANK_CRT_PEM value=${cli_crt_pem} + Set Environment Variable name=ANK_KEY_PEM value=${cli_key_pem} + +Setup Unix Socket + Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_URL} + Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_URL} + Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_URL} Run in foreground diff --git a/tests/resources/configs/ank-server.conf b/tests/resources/configs/ank-server.conf deleted file mode 100644 index bb88aaa54..000000000 --- a/tests/resources/configs/ank-server.conf +++ /dev/null @@ -1,2 +0,0 @@ -version = 'v1' -startup_manifest = 'tests/resources/configs/simple.yaml' diff --git a/tests/stests/configs/config_file.robot b/tests/stests/configs/config_file.robot index 757ea2f44..5fca17268 100644 --- a/tests/stests/configs/config_file.robot +++ b/tests/stests/configs/config_file.robot @@ -46,7 +46,7 @@ Test agent config file successful start-up Test server config overwrite manifest with cli arguments [Setup] Setup Ankaios # Preconditions - Ankaios server is started with manifest "${CONFIGS_DIR}/default.yaml" and server config file "${CONFIGS_DIR}/ank-server.conf" + Ankaios server is started with manifest "${CONFIGS_DIR}/default.yaml" And the CLI listens for workload states And Ankaios agent is started with name "agent_A" And the workload "sleepy" shall have the execution state "Running(Ok)" on agent "agent_A" From 70240bf0434031db56a532ef3476bc68b3586873 Mon Sep 17 00:00:00 2001 From: krucod3 <36224699+krucod3@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:43:28 +0000 Subject: [PATCH 23/55] Update utests to thread_local OPS to improve debugging --- grpc/src/server_unix_listener.rs | 183 ++++++++++++++----------------- 1 file changed, 80 insertions(+), 103 deletions(-) diff --git a/grpc/src/server_unix_listener.rs b/grpc/src/server_unix_listener.rs index 88ab3cf8c..64d7b5279 100644 --- a/grpc/src/server_unix_listener.rs +++ b/grpc/src/server_unix_listener.rs @@ -133,24 +133,14 @@ mod tests { use nix::unistd::Gid; use std::io; use std::path::PathBuf; - use std::sync::{Mutex, OnceLock}; fn socket_path() -> PathBuf { PathBuf::from(shim::SHARED_SOCKET_PATH) } - fn lock_shim() -> std::sync::MutexGuard<'static, ()> { - static TEST_LOCK: OnceLock> = OnceLock::new(); - TEST_LOCK - .get_or_init(|| Mutex::new(())) - .lock() - .expect("test lock poisoned") - } - // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_skips_existing_path_checks_when_path_is_missing() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); @@ -165,7 +155,6 @@ mod tests { // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_replaces_stale_socket_file() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); @@ -182,7 +171,6 @@ mod tests { // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_fails_when_removing_stale_socket_fails() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); @@ -208,7 +196,6 @@ mod tests { // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_fails_if_path_exists_and_is_not_socket() { - let _lock = lock_shim(); shim::reset_overrides(); let file_path = socket_path(); @@ -232,7 +219,6 @@ mod tests { // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_fails_when_existing_path_metadata_fails() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); @@ -260,7 +246,6 @@ mod tests { // [utest->swdd~server-configures-unix-domain-socket-group~1] #[test] fn utest_prepare_unix_listener_skips_group_configuration_without_group() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); @@ -276,7 +261,6 @@ mod tests { // [utest->swdd~server-configures-unix-domain-socket-group~1] #[test] fn utest_prepare_unix_listener_fails_for_unknown_group() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); shim::expect_group_from_name_once(GROUP_NAME, Ok(None)); @@ -293,7 +277,6 @@ mod tests { // [utest->swdd~server-configures-unix-domain-socket-group~1] #[test] fn utest_prepare_unix_listener_configures_group_permissions() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); @@ -314,7 +297,6 @@ mod tests { // [utest->swdd~server-configures-unix-domain-socket-group~1] #[test] fn utest_prepare_unix_listener_fails_when_chown_fails() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); @@ -340,7 +322,6 @@ mod tests { #[test] fn utest_prepare_unix_listener_fails_when_group_lookup_fails() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); shim::expect_group_from_name_once(GROUP_NAME, Err(Errno::EINVAL)); @@ -363,7 +344,6 @@ mod tests { // [utest->swdd~server-configures-unix-domain-socket-group~1] #[test] fn utest_prepare_unix_listener_fails_when_setting_permissions_fails() { - let _lock = lock_shim(); shim::reset_overrides(); let socket_path = socket_path(); @@ -393,9 +373,9 @@ mod tests { pub(crate) mod shim { use nix::unistd::Gid; + use std::cell::RefCell; use std::io; use std::path::Path; - use std::sync::{Mutex, MutexGuard, OnceLock}; pub(crate) const SHARED_SOCKET_PATH: &str = "/unit-test/shared.sock"; pub(crate) const GROUP_NAME: &str = "ankaios"; @@ -410,38 +390,35 @@ mod tests { calls: Vec<&'static str>, } - fn ops_store() -> MutexGuard<'static, Ops> { - static STORE: OnceLock> = OnceLock::new(); - STORE - .get_or_init(|| { - Mutex::new(Ops { - metadata: None, - remove_file: None, - set_permissions: None, - group_from_name: None, - chown: None, - calls: Vec::new(), - }) - }) - .lock() - .expect("test shim lock poisoned") + impl Ops { + fn new() -> Self { + Self { + metadata: None, + remove_file: None, + set_permissions: None, + group_from_name: None, + chown: None, + calls: Vec::new(), + } + } + } + + thread_local! { + static OPS: RefCell = RefCell::new(Ops::new()); + } + + fn with_ops(operation: impl FnOnce(&mut Ops) -> T) -> T { + OPS.with(|ops| operation(&mut ops.borrow_mut())) } pub(crate) fn reset_overrides() { - let mut ops = ops_store(); - *ops = Ops { - metadata: None, - remove_file: None, - set_permissions: None, - group_from_name: None, - chown: None, - calls: Vec::new(), - }; + with_ops(|ops| *ops = Ops::new()); } pub(crate) fn assert_calls(expected: &[&'static str]) { - let ops = ops_store(); - assert_eq!(ops.calls.as_slice(), expected, "unexpected call sequence"); + with_ops(|ops| { + assert_eq!(ops.calls.as_slice(), expected, "unexpected call sequence"); + }); } pub(crate) fn expect_metadata_once(path: &Path, result: io::Result) { @@ -450,7 +427,7 @@ mod tests { Path::new(SHARED_SOCKET_PATH), "unexpected metadata path" ); - ops_store().metadata = Some(result); + with_ops(|ops| ops.metadata = Some(result)); } pub(crate) fn expect_remove_file_once(path: &Path, result: io::Result<()>) { @@ -459,7 +436,7 @@ mod tests { Path::new(SHARED_SOCKET_PATH), "unexpected remove_file path" ); - ops_store().remove_file = Some(result); + with_ops(|ops| ops.remove_file = Some(result)); } pub(crate) fn expect_set_permissions_once(path: &Path, result: io::Result<()>) { @@ -468,7 +445,7 @@ mod tests { Path::new(SHARED_SOCKET_PATH), "unexpected set_permissions path" ); - ops_store().set_permissions = Some(result); + with_ops(|ops| ops.set_permissions = Some(result)); } pub(crate) fn expect_group_from_name_once( @@ -476,17 +453,17 @@ mod tests { result: Result, nix::Error>, ) { assert_eq!(name, GROUP_NAME, "unexpected group name lookup"); - ops_store().group_from_name = Some(result); + with_ops(|ops| ops.group_from_name = Some(result)); } pub(crate) fn expect_chown_once(path: &Path, gid: Gid, result: Result<(), nix::Error>) { assert_eq!(path, Path::new(SHARED_SOCKET_PATH), "unexpected chown path"); assert_eq!(gid, Gid::from_raw(GROUP_GID), "unexpected chown gid"); - ops_store().chown = Some(result); + with_ops(|ops| ops.chown = Some(result)); } pub(crate) mod fs { - use super::{Path, SHARED_SOCKET_PATH, ops_store}; + use super::{Path, SHARED_SOCKET_PATH, with_ops}; use std::io; #[derive(Clone, Copy)] @@ -533,49 +510,49 @@ mod tests { } pub fn metadata(path: &Path) -> io::Result { - let mut ops = ops_store(); - ops.calls.push("metadata"); - assert_eq!( - path, - Path::new(SHARED_SOCKET_PATH), - "unexpected metadata path" - ); - if let Some(result) = ops.metadata.take() { - return result; - } - panic!("missing metadata expectation for '{}'", path.display()); + with_ops(|ops| { + ops.calls.push("metadata"); + assert_eq!( + path, + Path::new(SHARED_SOCKET_PATH), + "unexpected metadata path" + ); + ops.metadata.take().unwrap_or_else(|| { + panic!("missing metadata expectation for '{}'", path.display()) + }) + }) } pub fn remove_file(path: &Path) -> io::Result<()> { - let mut ops = ops_store(); - ops.calls.push("remove_file"); - assert_eq!( - path, - Path::new(SHARED_SOCKET_PATH), - "unexpected remove_file path" - ); - if let Some(result) = ops.remove_file.take() { - return result; - } - panic!("missing remove_file expectation for '{}'", path.display()); + with_ops(|ops| { + ops.calls.push("remove_file"); + assert_eq!( + path, + Path::new(SHARED_SOCKET_PATH), + "unexpected remove_file path" + ); + ops.remove_file.take().unwrap_or_else(|| { + panic!("missing remove_file expectation for '{}'", path.display()) + }) + }) } pub fn set_permissions(path: &Path, perms: Permissions) -> io::Result<()> { - let mut ops = ops_store(); - ops.calls.push("set_permissions"); - assert_eq!(perms.mode, 0o660, "unexpected permission mode"); - assert_eq!( - path, - Path::new(SHARED_SOCKET_PATH), - "unexpected set_permissions path" - ); - if let Some(result) = ops.set_permissions.take() { - return result; - } - panic!( - "missing set_permissions expectation for '{}'", - path.display() - ); + with_ops(|ops| { + ops.calls.push("set_permissions"); + assert_eq!(perms.mode, 0o660, "unexpected permission mode"); + assert_eq!( + path, + Path::new(SHARED_SOCKET_PATH), + "unexpected set_permissions path" + ); + ops.set_permissions.take().unwrap_or_else(|| { + panic!( + "missing set_permissions expectation for '{}'", + path.display() + ) + }) + }) } } @@ -586,13 +563,13 @@ mod tests { impl Group { pub fn from_name(group_name: &str) -> Result, nix::Error> { - let mut ops = ops_store(); - ops.calls.push("group_from_name"); - assert_eq!(group_name, GROUP_NAME, "unexpected group name lookup"); - if let Some(result) = ops.group_from_name.take() { - return result; - } - panic!("missing group lookup expectation for '{group_name}'"); + with_ops(|ops| { + ops.calls.push("group_from_name"); + assert_eq!(group_name, GROUP_NAME, "unexpected group name lookup"); + ops.group_from_name.take().unwrap_or_else(|| { + panic!("missing group lookup expectation for '{group_name}'") + }) + }) } } @@ -602,14 +579,14 @@ mod tests { gid: Option, ) -> Result<(), nix::Error> { let gid = gid.expect("gid is required for test shim chown"); - let mut ops = ops_store(); - ops.calls.push("chown"); - if let Some(result) = ops.chown.take() { + with_ops(|ops| { + ops.calls.push("chown"); assert_eq!(path, Path::new(SHARED_SOCKET_PATH), "unexpected chown path"); assert_eq!(gid, Gid::from_raw(GROUP_GID), "unexpected chown gid"); - return result; - } - panic!("missing chown expectation for '{}'", path.display()); + ops.chown + .take() + .unwrap_or_else(|| panic!("missing chown expectation for '{}'", path.display())) + }) } } } From 53974e73185ee8bfef1eaa85726fb85816d086be Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:06:35 +0200 Subject: [PATCH 24/55] Update used socket file and run folder --- agent/config/ank-agent.conf | 2 +- {tools/aur => agent/config}/ank-agent.service | 4 +- ank/config/ank.conf | 2 +- server/config/ank-server.conf | 2 +- server/config/ank-server.service | 12 +++++ tools/aur/PKGBUILD-ankaios-bin.m4 | 9 ++-- tools/aur/PKGBUILD-ankaios-git.m4 | 10 ++-- tools/aur/PKGBUILD-ankaios.m4 | 10 ++-- tools/aur/README.md | 2 +- tools/aur/ank-server.service | 14 ------ tools/create_configs.sh | 6 ++- tools/debian/build_src_pkg.sh | 35 ++------------ tools/install.sh | 47 ++++--------------- 13 files changed, 50 insertions(+), 105 deletions(-) rename {tools/aur => agent/config}/ank-agent.service (66%) create mode 100644 server/config/ank-server.service delete mode 100644 tools/aur/ank-server.service diff --git a/agent/config/ank-agent.conf b/agent/config/ank-agent.conf index 73b34b73d..9df182a7e 100644 --- a/agent/config/ank-agent.conf +++ b/agent/config/ank-agent.conf @@ -11,7 +11,7 @@ version = 'v1' name = 'agent_A' # The server endpoint. -address = 'unix:///run/ankaios/server.sock' +address = 'unix:///run/ankaios.sock' # An existing path where to manage the fifo files. # If not set, defaults to '$TMPDIR/ankaios/' (falls back to '/tmp/ankaios/' if TMPDIR is not set). diff --git a/tools/aur/ank-agent.service b/agent/config/ank-agent.service similarity index 66% rename from tools/aur/ank-agent.service rename to agent/config/ank-agent.service index 628b21238..c4315131d 100644 --- a/tools/aur/ank-agent.service +++ b/agent/config/ank-agent.service @@ -5,7 +5,9 @@ Wants=network.target [Service] Environment="RUST_LOG=info" -ExecStart=/usr/bin/ank-agent +RuntimeDirectory=ankaios +RuntimeDirectoryMode=0700 +ExecStart=/usr/local/bin/ank-agent [Install] WantedBy=default.target diff --git a/ank/config/ank.conf b/ank/config/ank.conf index a479f3809..0f4d3811b 100644 --- a/ank/config/ank.conf +++ b/ank/config/ank.conf @@ -20,7 +20,7 @@ version = 'v1' # The endpoint to Ankaios server. # If started in insecure mode then the HTTP protocol shall be used, # otherwise the HTTPS protocol shall be used. -address = 'unix:///run/ankaios/server.sock' +address = 'unix:///run/ankaios.sock' # The flag to disable TLS communication with the server. # If set to 'true', then the CLI shall not use TLS. diff --git a/server/config/ank-server.conf b/server/config/ank-server.conf index 5e4b5279e..126aa85d9 100644 --- a/server/config/ank-server.conf +++ b/server/config/ank-server.conf @@ -9,7 +9,7 @@ version = 'v1' startup_manifest = '/etc/ankaios/state.yaml' # The address, including the port, to which the server should listen. -address = 'unix:///run/ankaios/server.sock' +address = 'unix:///run/ankaios.sock' # Optional socket group applied to Unix domain socket files. # This option is only valid with unix:// addresses. diff --git a/server/config/ank-server.service b/server/config/ank-server.service new file mode 100644 index 000000000..b6f03967d --- /dev/null +++ b/server/config/ank-server.service @@ -0,0 +1,12 @@ +[Unit] +Description=Ankaios server +After=network.target +Wants=network.target + +[Service] +Environment="RUST_LOG=info" +ExecStart=/usr/local/bin/ank-server +ExecStopPost=-/bin/rm -f /run/ankaios.sock + +[Install] +WantedBy=default.target diff --git a/tools/aur/PKGBUILD-ankaios-bin.m4 b/tools/aur/PKGBUILD-ankaios-bin.m4 index 540222574..f0854353e 100644 --- a/tools/aur/PKGBUILD-ankaios-bin.m4 +++ b/tools/aur/PKGBUILD-ankaios-bin.m4 @@ -11,19 +11,20 @@ license=('Apache-2.0') depends=('libgcc' 'glibc') source=("$pkgbase-${pkgver}_configs.tar.gz::https://github.com/eclipse-ankaios/ankaios/releases/download/v$pkgver/ankaios_configs.tar.gz" "$pkgbase-${pkgver}_man-pages.tar.gz::https://github.com/eclipse-ankaios/ankaios/releases/download/v$pkgver/man-pages.tar.gz" - 'ank-server.service' - 'ank-agent.service' 'ankaios.sysusers') source_x86_64=("$pkgbase-$pkgver-x86_64.tar.gz::https://github.com/eclipse-ankaios/ankaios/releases/download/v$pkgver/ankaios-linux-amd64.tar.gz") source_aarch64=("$pkgbase-$pkgver-aarch64.tar.gz::https://github.com/eclipse-ankaios/ankaios/releases/download/v$pkgver/ankaios-linux-arm64.tar.gz") b2sums=('xxxxx' - 'xxxxx' - 'xxxxx' 'xxxxx' 'xxxxx') b2sums_x86_64=('xxxxx') b2sums_aarch64=('xxxxx') +prepare() { + # The released config archive ships the unit files with ExecStart=/usr/local/bin/...; adapt to the AUR package layout + sed -i 's#/usr/local/bin#/usr/bin#' ank-server.service ank-agent.service +} + package_ankaios-server-bin() { pkgdesc="The server application of Eclipse Ankaios" provides=(ankaios-server) diff --git a/tools/aur/PKGBUILD-ankaios-git.m4 b/tools/aur/PKGBUILD-ankaios-git.m4 index 2451b2a27..6d01cb27c 100644 --- a/tools/aur/PKGBUILD-ankaios-git.m4 +++ b/tools/aur/PKGBUILD-ankaios-git.m4 @@ -11,12 +11,8 @@ license=('Apache-2.0') depends=('libgcc' 'glibc') makedepends=('cargo' 'git' 'protobuf' 'help2man') source=("$pkgbase::git+https://github.com/eclipse-ankaios/ankaios.git" - 'ank-server.service' - 'ank-agent.service' 'ankaios.sysusers') b2sums=('SKIP' - 'xxxxx' - 'xxxxx' 'xxxxx') pkgver() { @@ -28,6 +24,8 @@ prepare() { cd "$pkgbase" export RUSTUP_TOOLCHAIN=stable cargo fetch --locked --target host-tuple + # Unit files ship with ExecStart=/usr/local/bin/...; adapt to the AUR package layout + sed -i 's#/usr/local/bin#/usr/bin#' server/config/ank-server.service agent/config/ank-agent.service } build() { @@ -47,7 +45,7 @@ package_ankaios-server-git() { "etc/ankaios/state.yaml") install -Dm755 -t "$pkgdir"/usr/bin/ "$pkgbase/target/$(rustc --print host-tuple)/release/ank-server" - install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ ank-server.service + install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ "$pkgbase"/server/config/ank-server.service install -Dm644 ankaios.sysusers "$pkgdir"/usr/lib/sysusers.d/ankaios.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ "$pkgbase"/server/config/ank-server.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ "$pkgbase"/server/config/state.yaml @@ -65,7 +63,7 @@ package_ankaios-agent-git() { ) install -Dm755 -t "$pkgdir"/usr/bin/ "$pkgbase/target/$(rustc --print host-tuple)/release/ank-agent" - install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ ank-agent.service + install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ "$pkgbase"/agent/config/ank-agent.service install -Dm644 -t "$pkgdir"/etc/ankaios/ "$pkgbase"/agent/config/ank-agent.conf install -Dm644 -t "$pkgdir"/usr/share/man/man8 "$pkgbase"/build/man/man8/ank-agent.8 } diff --git a/tools/aur/PKGBUILD-ankaios.m4 b/tools/aur/PKGBUILD-ankaios.m4 index 438f8cc31..bcd388bb0 100644 --- a/tools/aur/PKGBUILD-ankaios.m4 +++ b/tools/aur/PKGBUILD-ankaios.m4 @@ -11,18 +11,16 @@ license=('Apache-2.0') depends=('libgcc' 'glibc') makedepends=('cargo' 'protobuf' 'help2man') source=("$pkgbase-$pkgver.tar.gz::https://github.com/eclipse-ankaios/ankaios/archive/refs/tags/v$pkgver.tar.gz" - 'ank-server.service' - 'ank-agent.service' 'ankaios.sysusers') b2sums=('xxxxxx' - 'xxxxxx' - 'xxxxxx' 'xxxxxx') prepare() { cd "ankaios-$pkgver" export RUSTUP_TOOLCHAIN=stable cargo fetch --locked --target host-tuple + # Unit files ship with ExecStart=/usr/local/bin/...; adapt to the AUR package layout + sed -i 's#/usr/local/bin#/usr/bin#' server/config/ank-server.service agent/config/ank-agent.service } build() { @@ -40,7 +38,7 @@ package_ankaios-server() { "etc/ankaios/state.yaml") install -Dm755 -t "$pkgdir"/usr/bin/ "ankaios-$pkgver/target/$(rustc --print host-tuple)/release/ank-server" - install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ ank-server.service + install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ "ankaios-$pkgver"/server/config/ank-server.service install -Dm644 ankaios.sysusers "$pkgdir"/usr/lib/sysusers.d/ankaios.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ "ankaios-$pkgver"/server/config/ank-server.conf install -Dm644 -t "$pkgdir"/etc/ankaios/ "ankaios-$pkgver"/server/config/state.yaml @@ -56,7 +54,7 @@ package_ankaios-agent() { ) install -Dm755 -t "$pkgdir"/usr/bin/ "ankaios-$pkgver/target/$(rustc --print host-tuple)/release/ank-agent" - install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ ank-agent.service + install -Dm644 -t "$pkgdir"/usr/lib/systemd/system/ "ankaios-$pkgver"/agent/config/ank-agent.service install -Dm644 -t "$pkgdir"/etc/ankaios/ "ankaios-$pkgver"/agent/config/ank-agent.conf install -Dm644 -t "$pkgdir"/usr/share/man/man8 "ankaios-$pkgver"/build/man/man8/ank-agent.8 } diff --git a/tools/aur/README.md b/tools/aur/README.md index 100f66543..b99ce1b4e 100644 --- a/tools/aur/README.md +++ b/tools/aur/README.md @@ -18,7 +18,7 @@ They are intended to be run manually by the package maintainer outside the dev c | `PKGBUILD-ankaios.m4` | Template for the release-from-source AUR package | | `PKGBUILD-ankaios-bin.m4` | Template for the prebuilt-binary AUR package | | `PKGBUILD-ankaios-git.m4` | Template for the git-head AUR package | -| `ank-server.service`, `ank-agent.service`, `ankaios-cli.install`, `ankaios.sysusers` | Asset files copied into each AUR package repository | +| `ankaios-cli.install`, `ankaios.sysusers` | Asset files copied into each AUR package repository | | `ankaios.sysusers` | `systemd-sysusers` fragment creating the system group `ankaios` used for the server's Unix domain socket (`/run/ankaios/server.sock`), installed to `/usr/lib/sysusers.d/ankaios.conf` | ## Required environment diff --git a/tools/aur/ank-server.service b/tools/aur/ank-server.service deleted file mode 100644 index 10fa0a597..000000000 --- a/tools/aur/ank-server.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=Ankaios server -After=network.target -Wants=network.target - -[Service] -Environment="RUST_LOG=info" -ExecStartPre=/usr/bin/mkdir -p /run/ankaios -ExecStartPre=/usr/bin/chgrp ankaios /run/ankaios -ExecStartPre=/usr/bin/chmod 0750 /run/ankaios -ExecStart=/usr/bin/ank-server - -[Install] -WantedBy=default.target diff --git a/tools/create_configs.sh b/tools/create_configs.sh index 7b9f4150c..0f0e8cae9 100755 --- a/tools/create_configs.sh +++ b/tools/create_configs.sh @@ -8,6 +8,8 @@ SERVER_CONFIG_FILE="${ROOT_DIR}/server/config/ank-server.conf" AGENT_CONFIG_FILE="${ROOT_DIR}/agent/config/ank-agent.conf" ANK_CONFIG_FILE="${ROOT_DIR}/ank/config/ank.conf" STATE_FILE="${ROOT_DIR}/server/config/state.yaml" +SERVER_SERVICE_FILE="${ROOT_DIR}/server/config/ank-server.service" +AGENT_SERVICE_FILE="${ROOT_DIR}/agent/config/ank-agent.service" DIST_DIR="${ROOT_DIR}/dist" cd "${DIST_DIR}" @@ -15,7 +17,9 @@ tar -cvzf "${CONFIG_FILES_NAME_BASE}".tar.gz \ -C "$(dirname "$SERVER_CONFIG_FILE")" "$(basename "$SERVER_CONFIG_FILE")" \ -C "$(dirname "$AGENT_CONFIG_FILE")" "$(basename "$AGENT_CONFIG_FILE")" \ -C "$(dirname "$ANK_CONFIG_FILE")" "$(basename "$ANK_CONFIG_FILE")" \ - -C "$(dirname "$STATE_FILE")" "$(basename "$STATE_FILE")" + -C "$(dirname "$STATE_FILE")" "$(basename "$STATE_FILE")" \ + -C "$(dirname "$SERVER_SERVICE_FILE")" "$(basename "$SERVER_SERVICE_FILE")" \ + -C "$(dirname "$AGENT_SERVICE_FILE")" "$(basename "$AGENT_SERVICE_FILE")" echo "Creating checksums" sha512sum "${CONFIG_FILES_NAME_BASE}".tar.gz > "${CONFIG_FILES_NAME_BASE}".tar.gz.sha512sum.txt diff --git a/tools/debian/build_src_pkg.sh b/tools/debian/build_src_pkg.sh index 5ca8e1380..35de6ee07 100755 --- a/tools/debian/build_src_pkg.sh +++ b/tools/debian/build_src_pkg.sh @@ -39,7 +39,6 @@ fi SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) BASE_DIR="$SCRIPT_DIR/../.." MAINTAINER="Eclipse Ankaios " -LOG_LEVEL="info" RELEASE_URL="https://github.com/eclipse-ankaios/ankaios/releases/tag/v$ANKAIOS_VERSION" ANK_SYSTEM_GROUP="ankaios" @@ -103,37 +102,9 @@ EOF } write_services() { - cat > "$BASE_DIR/debian/ank-server.service" << EOF -[Unit] -Description=Ankaios server -After=network.target -Wants=network.target - -[Service] -Environment="RUST_LOG=${LOG_LEVEL}" -ExecStartPre=/usr/bin/mkdir -p /run/ankaios -ExecStartPre=/usr/bin/chgrp ${ANK_SYSTEM_GROUP} /run/ankaios -ExecStartPre=/usr/bin/chmod 0750 /run/ankaios -ExecStart=/usr/bin/ank-server - -[Install] -WantedBy=default.target -EOF - - cat > "$BASE_DIR/debian/ank-agent.service" << EOF -[Unit] -Description=Ankaios agent -After=network.target ank-server.service -Wants=network.target - -[Service] -Environment="RUST_LOG=${LOG_LEVEL}" -ExecStart=/usr/bin/ank-agent - -[Install] -WantedBy=default.target -EOF - + # Reuse the canonical unit files, just adapting the install path for the debian package layout + sed 's#/usr/local/bin#/usr/bin#' "$BASE_DIR/server/config/ank-server.service" > "$BASE_DIR/debian/ank-server.service" + sed 's#/usr/local/bin#/usr/bin#' "$BASE_DIR/agent/config/ank-agent.service" > "$BASE_DIR/debian/ank-agent.service" } write_postinst() { diff --git a/tools/install.sh b/tools/install.sh index 8fe41869f..782a81ca3 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -168,6 +168,10 @@ fi echo "Extracting the binaries into install folder: '${BIN_DESTINATION}'" ${BIN_SUDO} tar --no-same-owner -m -xvzf "${RELEASE_FILE_NAME}" -C "${BIN_DESTINATION}/" +CONFIGS_FILE_NAME="ankaios_configs.tar.gz" +echo "Downloading the configs: '${ANKAIOS_CONFIGS_URL}'" +download_release "${ANKAIOS_CONFIGS_URL}" + if [[ "$INSTALL_TYPE" == server || "$INSTALL_TYPE" == both ]]; then create_ankaios_system_group fi @@ -180,40 +184,15 @@ if [ -d "$SERVICE_DEST" ]; then fi if [[ "$INSTALL_TYPE" == server || "$INSTALL_TYPE" == both ]]; then - $SVC_SUDO tee "$FILE_ANK_SERVER_SERVICE" >/dev/null << EOF -[Unit] -Description=Ankaios server -After=network.target -Wants=network.target - -[Service] -Environment="RUST_LOG=${INSTALL_ANK_SERVER_RUST_LOG}" -ExecStartPre=/usr/bin/mkdir -p /run/ankaios -ExecStartPre=/usr/bin/chgrp ${ANK_SYSTEM_GROUP} /run/ankaios -ExecStartPre=/usr/bin/chmod 0750 /run/ankaios -ExecStart=${BIN_DESTINATION}/ank-server - -[Install] -WantedBy=default.target -EOF - echo "Start server with 'sudo systemctl start $ANK_SERVER_SERVICE'" + tar -xzf "${CONFIGS_FILE_NAME}" -C "${ANKAIOS_TMP_DIR}" ank-server.service + sed "s#RUST_LOG=[^\"]*#RUST_LOG=${INSTALL_ANK_SERVER_RUST_LOG}#" "${ANKAIOS_TMP_DIR}/ank-server.service" | $SVC_SUDO tee "$FILE_ANK_SERVER_SERVICE" >/dev/null + echo "Start server with 'sudo systemctl start $ANK_SERVER_SERVICE'" fi if [[ "$INSTALL_TYPE" == agent || "$INSTALL_TYPE" == both ]]; then - $SVC_SUDO tee "$FILE_ANK_AGENT_SERVICE" >/dev/null << EOF -[Unit] -Description=Ankaios agent -After=network.target ${ANK_SERVER_SERVICE}.service -Wants=network.target - -[Service] -Environment="RUST_LOG=${INSTALL_ANK_AGENT_RUST_LOG}" -ExecStart=${BIN_DESTINATION}/ank-agent - -[Install] -WantedBy=default.target -EOF - echo "Start agent with 'sudo systemctl start $ANK_AGENT_SERVICE'" + tar -xzf "${CONFIGS_FILE_NAME}" -C "${ANKAIOS_TMP_DIR}" ank-agent.service + sed "s#RUST_LOG=[^\"]*#RUST_LOG=${INSTALL_ANK_AGENT_RUST_LOG}#" "${ANKAIOS_TMP_DIR}/ank-agent.service" | $SVC_SUDO tee "$FILE_ANK_AGENT_SERVICE" >/dev/null + echo "Start agent with 'sudo systemctl start $ANK_AGENT_SERVICE'" fi else @@ -255,12 +234,6 @@ EOF fi fi -echo "Downloading the configs: '${ANKAIOS_CONFIGS_URL}'" -download_release "${ANKAIOS_CONFIGS_URL}" - -# Extract the config files -CONFIGS_FILE_NAME="ankaios_configs.tar.gz" - # Never overwrites an existing config file, instead the new default is extracted as '.confnew' install_config_file() { local sudo_cmd="$1" From 098d3230572685694a9381d416fa721968129c14 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:48:03 +0200 Subject: [PATCH 25/55] Improve description insecure flag Co-authored-by: Kaloyan <36224699+krucod3@users.noreply.github.com> --- agent/config/ank-agent.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent/config/ank-agent.conf b/agent/config/ank-agent.conf index 9df182a7e..37cfd049b 100644 --- a/agent/config/ank-agent.conf +++ b/agent/config/ank-agent.conf @@ -21,7 +21,8 @@ address = 'unix:///run/ankaios.sock' # The flag to disable TLS communication with the server. # If set to 'true', then the agent shall not use TLS. -# This option must not be used with unix:// addresses. +# This option is only available when using http communication. +# insecure = false # The list of enabled runtimes. If the array is missing # all runtimes will be enabled. From f68cc563c0634ce5a60c9d2190268665c4fbc050 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 14:11:29 +0200 Subject: [PATCH 26/55] Improve handling of differnt connection type and rename to URL --- agent/src/agent_config.rs | 48 +++---- agent/src/cli.rs | 5 +- agent/src/main.rs | 6 +- ank/src/ank_config.rs | 52 +++---- ank/src/cli.rs | 4 +- ank/src/cli_commands.rs | 14 +- ank/src/cli_commands/server_connection.rs | 3 +- ank/src/main.rs | 12 +- common/src/config.rs | 163 +++++++++++++++------- grpc/src/client.rs | 152 +++++++------------- grpc/tests/grpc_test.rs | 3 +- server/src/server_config.rs | 18 +-- 12 files changed, 243 insertions(+), 237 deletions(-) diff --git a/agent/src/agent_config.rs b/agent/src/agent_config.rs index df9f9654d..249db9821 100644 --- a/agent/src/agent_config.rs +++ b/agent/src/agent_config.rs @@ -16,8 +16,7 @@ use crate::cli::Arguments; use crate::io_utils::default_run_folder_string; use common::config::{ - CONFIG_VERSION, ConfigFile, ConversionErrors, get_default_server_url, - validate_server_address_format, validate_unix_socket_tls_settings, + CONFIG_VERSION, ConfigFile, ConversionErrors, ServerUrl, get_default_url, }; use common::std_extensions::UnreachableOption; @@ -42,8 +41,8 @@ pub struct AgentConfig { pub version: String, #[serde(default)] pub name: String, - #[serde(default = "get_default_server_url", alias = "server_url")] - pub address: String, + #[serde(default = "get_default_url")] + pub server_url: ServerUrl, #[serde(default = "crate::io_utils::default_run_folder_string")] pub run_folder: String, #[serde(default)] @@ -65,7 +64,7 @@ impl Default for AgentConfig { AgentConfig { version: CONFIG_VERSION.to_string(), name: String::new(), - address: get_default_server_url(), + server_url: get_default_url(), run_folder: default_run_folder_string(), insecure: bool::default(), runtimes: None, @@ -117,9 +116,6 @@ impl ConfigFile for AgentConfig { agent_config.key_pem_content = Some(key_pem_content); } - validate_server_address_format(&agent_config.address) - .map_err(ConversionErrors::InvalidConfig)?; - validate_socket_configuration(&agent_config).map_err(ConversionErrors::InvalidConfig)?; Ok(agent_config) @@ -132,8 +128,8 @@ impl AgentConfig { self.name = name.to_string(); } - if let Some(address) = &args.address { - self.address = address.to_string(); + if let Some(server_url) = &args.server_url { + self.server_url = server_url.parse()?; } if let Some(run_folder) = &args.run_folder { @@ -167,7 +163,6 @@ impl AgentConfig { self.tags = tags.iter().cloned().collect(); } - validate_server_address_format(&self.address)?; validate_socket_configuration(self)?; Ok(()) @@ -175,9 +170,8 @@ impl AgentConfig { } fn validate_socket_configuration(agent_config: &AgentConfig) -> Result<(), String> { - validate_unix_socket_tls_settings( + agent_config.server_url.validate_tls_settings( "agent", - &agent_config.address, agent_config.insecure, agent_config.ca_pem.is_some() || agent_config.crt_pem.is_some() @@ -201,10 +195,10 @@ mod tests { use super::{AgentConfig, CONFIG_VERSION}; use crate::cli::Arguments; use crate::io_utils::default_run_folder_string; - use common::config::{ConfigFile, ConversionErrors}; + use common::config::{ConfigFile, ConversionErrors, ServerUrl, get_default_url}; use ankaios_api::test_utils::fixtures; - use common::{DEFAULT_SERVER_ADDRESS, config::get_default_server_url}; + use common::DEFAULT_SERVER_ADDRESS; use std::io::Write; use std::path::PathBuf; @@ -239,7 +233,7 @@ mod tests { fn utest_default_agent_config() { let default_agent_config = AgentConfig::default(); - assert_eq!(default_agent_config.address, get_default_server_url()); + assert_eq!(default_agent_config.server_url, get_default_url()); assert!(!default_agent_config.insecure); assert_eq!(default_agent_config.version, CONFIG_VERSION); } @@ -296,7 +290,7 @@ mod tests { let args = Arguments { config_path: None, agent_name: Some(fixtures::AGENT_NAMES[0].to_string()), - address: Some(DEFAULT_SERVER_ADDRESS.to_string()), + server_url: Some(DEFAULT_SERVER_ADDRESS.to_string()), run_folder: Some(default_run_folder.clone()), insecure: Some(false), tags: None, @@ -308,7 +302,10 @@ mod tests { agent_config.update_with_args(&args).unwrap(); assert_eq!(agent_config.name, fixtures::AGENT_NAMES[0].to_string()); - assert_eq!(agent_config.address, DEFAULT_SERVER_ADDRESS.to_string()); + assert_eq!( + agent_config.server_url, + ServerUrl::Tcp(DEFAULT_SERVER_ADDRESS.to_string()) + ); assert_eq!(agent_config.run_folder, default_run_folder); assert!(!agent_config.insecure); assert_eq!(agent_config.ca_pem, Some(fixtures::CA_PEM_PATH.to_string())); @@ -358,7 +355,7 @@ mod tests { let args = Arguments { config_path: None, agent_name: Some(fixtures::AGENT_NAMES[0].to_string()), - address: Some(DEFAULT_SERVER_ADDRESS.to_string()), + server_url: Some(DEFAULT_SERVER_ADDRESS.to_string()), run_folder: Some(default_run_folder), insecure: Some(false), tags: None, @@ -391,7 +388,7 @@ mod tests { r"# version = 'v1' name = '{}' - address = 'https://127.0.0.1:25551' + server_url = 'https://127.0.0.1:25551' run_folder = '{}' insecure = true ca_pem_content = '''{}''' @@ -415,7 +412,10 @@ mod tests { let agent_config = agent_config_res.unwrap(); assert_eq!(agent_config.name, fixtures::AGENT_NAMES[0].to_string()); - assert_eq!(agent_config.address, DEFAULT_SERVER_ADDRESS.to_string()); + assert_eq!( + agent_config.server_url, + ServerUrl::Tcp(DEFAULT_SERVER_ADDRESS.to_string()) + ); assert_eq!(agent_config.run_folder, default_run_folder_string()); assert!(agent_config.insecure); assert_eq!( @@ -443,7 +443,7 @@ mod tests { let args = Arguments { config_path: None, agent_name: None, - address: None, + server_url: None, run_folder: None, insecure: None, ca_pem: None, @@ -466,7 +466,7 @@ mod tests { fn utest_agent_config_rejects_insecure_with_unix_domain_socket() { let agent_config_content = r"# version = 'v1' - address = 'unix:///tmp/ankaios-agent.sock' + server_url = 'unix:///tmp/ankaios-agent.sock' insecure = true #"; @@ -481,7 +481,7 @@ mod tests { fn utest_agent_config_rejects_tls_with_unix_domain_socket() { let agent_config_content = r"# version = 'v1' - address = 'unix:///tmp/ankaios-agent.sock' + server_url = 'unix:///tmp/ankaios-agent.sock' ca_pem = '/tmp/.certs/ca.pem' #"; diff --git a/agent/src/cli.rs b/agent/src/cli.rs index c203bfa3b..379e4d699 100644 --- a/agent/src/cli.rs +++ b/agent/src/cli.rs @@ -33,14 +33,13 @@ pub struct Arguments { pub agent_name: Option, #[arg( short = 's', - long = "address", - alias = "server-url", + long = "server-url", required = false, env = "ANKAGENT_SERVER_URL" )] /// The server endpoint. /// Supported values are https://host:port and unix:///path/to/socket. - pub address: Option, + pub server_url: Option, #[arg(short = 'r', long = "run-folder", required = false)] /// An existing directory where agent specific runtime files will be stored. If not specified, a default folder is created. /// If the directory already exists, it must be owned by the user running the agent and not be accessible by other users or groups. diff --git a/agent/src/main.rs b/agent/src/main.rs index 172bfdd52..d4580b5b9 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -134,7 +134,7 @@ async fn main() { log::debug!( "Starting the Ankaios agent with \n\tname: '{}', \n\tserver endpoint: '{}', \n\trun directory: '{}'", agent_config.name, - agent_config.address, + agent_config.server_url, agent_config.run_folder, ); @@ -193,7 +193,7 @@ async fn main() { workload_state_sender, ); - let tls_config = if agent_config.address.starts_with("unix://") { + let tls_config = if agent_config.server_url.is_unix() { None } else { if let Err(err_message) = TLSConfig::is_config_conflicting( @@ -219,7 +219,7 @@ async fn main() { let mut communications_client = GRPCCommunicationsClient::new_agent_communication( agent_config.name.clone(), - agent_config.address, + agent_config.server_url, agent_config.tags, tls_config, ) diff --git a/ank/src/ank_config.rs b/ank/src/ank_config.rs index 013726151..57901f1f5 100644 --- a/ank/src/ank_config.rs +++ b/ank/src/ank_config.rs @@ -15,8 +15,7 @@ use crate::cli::AnkCli; use crate::output_warn; use common::config::{ - CONFIG_VERSION, ConfigFile, ConversionErrors, get_default_server_url, - validate_server_address_format, validate_unix_socket_tls_settings, + CONFIG_VERSION, ConfigFile, ConversionErrors, ServerUrl, get_default_url, }; use common::std_extensions::UnreachableOption; @@ -75,7 +74,7 @@ pub struct AnkConfig { pub verbose: bool, pub quiet: bool, pub no_wait: bool, - pub address: String, + pub server_url: ServerUrl, pub insecure: bool, ca_pem: Option, crt_pem: Option, @@ -96,8 +95,8 @@ struct AnkConfigHelper { quiet: bool, #[serde(default)] no_wait: bool, - #[serde(default = "get_default_server_url", alias = "server_url")] - address: String, + #[serde(default = "get_default_url")] + server_url: ServerUrl, #[serde(default)] insecure: bool, ca_pem: Option, @@ -116,7 +115,7 @@ impl From for AnkConfig { verbose: helper.verbose, quiet: helper.quiet, no_wait: helper.no_wait, - address: helper.address, + server_url: helper.server_url, insecure: helper.insecure, ca_pem: helper.ca_pem, crt_pem: helper.crt_pem, @@ -189,7 +188,7 @@ impl Default for AnkConfig { verbose: bool::default(), quiet: bool::default(), no_wait: bool::default(), - address: get_default_server_url(), + server_url: get_default_url(), insecure: bool::default(), ca_pem: None, crt_pem: None, @@ -238,9 +237,6 @@ impl ConfigFile for AnkConfig { ank_config.key_pem_content = Some(key_pem_content); } - validate_server_address_format(&ank_config.address) - .map_err(ConversionErrors::InvalidConfig)?; - validate_socket_configuration(&ank_config).map_err(ConversionErrors::InvalidConfig)?; Ok(ank_config) @@ -266,8 +262,8 @@ impl AnkConfig { self.insecure = insecure; } - if let Some(address) = &args.address { - self.address = address.to_owned(); + if let Some(server_url) = &args.server_url { + self.server_url = server_url.parse()?; } if let Some(ca_pem_path) = &args.ca_pem { @@ -289,16 +285,14 @@ impl AnkConfig { self.key_pem_content = Some(key_pem_content); } - validate_server_address_format(&self.address)?; validate_socket_configuration(self)?; Ok(()) } } fn validate_socket_configuration(ank_config: &AnkConfig) -> Result<(), String> { - validate_unix_socket_tls_settings( + ank_config.server_url.validate_tls_settings( "ank", - &ank_config.address, ank_config.insecure, ank_config.ca_pem.is_some() || ank_config.crt_pem.is_some() @@ -326,8 +320,8 @@ mod tests { }; use ankaios_api::test_utils::fixtures; - use common::config::{ConfigFile, ConversionErrors}; - use common::{DEFAULT_SERVER_ADDRESS, config::get_default_server_url}; + use common::DEFAULT_SERVER_ADDRESS; + use common::config::{ConfigFile, ConversionErrors, ServerUrl, get_default_url}; use std::io::Write; use std::path::PathBuf; @@ -436,7 +430,7 @@ mod tests { object_field_mask: Vec::new(), }), }), - address: Some(TEST_SERVER_URL.to_string()), + server_url: Some(TEST_SERVER_URL.to_string()), config_path: Some(super::get_config_file_paths()[0].clone()), response_timeout_ms: Some(5000), insecure: Some(false), @@ -455,7 +449,10 @@ mod tests { assert!(ank_config.quiet); assert!(ank_config.no_wait); assert!(!ank_config.insecure); - assert_eq!(ank_config.address, TEST_SERVER_URL.to_string()); + assert_eq!( + ank_config.server_url, + ServerUrl::Tcp(TEST_SERVER_URL.to_string()) + ); assert_eq!(ank_config.ca_pem, Some(fixtures::CA_PEM_PATH.to_string())); assert_eq!(ank_config.crt_pem, Some(fixtures::CRT_PEM_PATH.to_string())); assert_eq!(ank_config.key_pem, Some(fixtures::KEY_PEM_PATH.to_string())); @@ -500,7 +497,7 @@ mod tests { object_field_mask: Vec::new(), }), }), - address: Some(DEFAULT_SERVER_ADDRESS.to_string()), + server_url: Some(DEFAULT_SERVER_ADDRESS.to_string()), config_path: Some(super::get_config_file_paths()[0].clone()), response_timeout_ms: Some(5000), insecure: Some(false), @@ -555,7 +552,7 @@ mod tests { object_field_mask: Vec::new(), }), }), - address: Some(DEFAULT_SERVER_ADDRESS.to_string()), + server_url: Some(DEFAULT_SERVER_ADDRESS.to_string()), config_path: Some(super::get_config_file_paths()[0].clone()), response_timeout_ms: Some(5000), insecure: None, @@ -586,7 +583,7 @@ mod tests { let ank_config = AnkConfig::from_file(PathBuf::from(tmp_config_file.path())).unwrap(); - assert_eq!(ank_config.address, get_default_server_url()); + assert_eq!(ank_config.server_url, get_default_url()); assert!(!ank_config.insecure); assert!(ank_config.ca_pem.is_none()); assert!(ank_config.crt_pem.is_none()); @@ -623,7 +620,7 @@ mod tests { quiet = false no_wait = false [default] - address = 'https://127.0.0.1:25551' + server_url = 'https://127.0.0.1:25551' insecure = false ca_pem_content = '''{}''' crt_pem_content = '''{}''' @@ -647,7 +644,10 @@ mod tests { assert!(!ank_config.verbose); assert!(!ank_config.quiet); assert!(!ank_config.no_wait); - assert_eq!(ank_config.address, DEFAULT_SERVER_ADDRESS.to_string()); + assert_eq!( + ank_config.server_url, + ServerUrl::Tcp(DEFAULT_SERVER_ADDRESS.to_string()) + ); assert!(!ank_config.insecure); assert_eq!( ank_config.ca_pem_content, @@ -724,7 +724,7 @@ mod tests { let ank_config_content = r"# version = 'v1' [default] - address = 'unix:///tmp/ankaios-cli.sock' + server_url = 'unix:///tmp/ankaios-cli.sock' insecure = true #"; @@ -741,7 +741,7 @@ mod tests { r"# version = 'v1' [default] - address = 'unix:///tmp/ankaios-cli.sock' + server_url = 'unix:///tmp/ankaios-cli.sock' ca_pem_content = '''{}''' #", fixtures::CA_PEM_CONTENT, diff --git a/ank/src/cli.rs b/ank/src/cli.rs index cb5ad4b09..381cc2613 100644 --- a/ank/src/cli.rs +++ b/ank/src/cli.rs @@ -191,10 +191,10 @@ pub struct AnkCli { /// The path to the server config file. /// The default paths are $HOME/.config/ankaios/ank.conf and /etc/ankaios/ank.conf with first existing file taking precedence over the other. pub config_path: Option, - #[arg(short = 's', long = "address", alias = "server-url", required=false, env = ANK_SERVER_URL_ENV_KEY)] + #[arg(short = 's', long = "server-url", required=false, env = ANK_SERVER_URL_ENV_KEY)] /// The endpoint to Ankaios server. /// Supported values are https://host:port and unix:///path/to/socket. - pub address: Option, + pub server_url: Option, #[arg( long = "response-timeout", required = false, diff --git a/ank/src/cli_commands.rs b/ank/src/cli_commands.rs index 25da9c585..4d3e520e6 100644 --- a/ank/src/cli_commands.rs +++ b/ank/src/cli_commands.rs @@ -46,7 +46,8 @@ use ankaios_api::ank_base::{ WorkloadStatesMap, }; use common::{ - communications_error::CommunicationMiddlewareError, from_server_interface::FromServer, + communications_error::CommunicationMiddlewareError, config::ServerUrl, + from_server_interface::FromServer, }; use wait_list_display::WaitListDisplay; @@ -170,7 +171,7 @@ impl CliCommands { pub fn init( response_timeout_ms: u64, cli_name: String, - server_url: String, + server_url: ServerUrl, no_wait: bool, tls_config: Option, ) -> Result { @@ -178,7 +179,7 @@ impl CliCommands { no_wait, server_connection: ServerConnection::new( cli_name.as_str(), - server_url.clone(), + server_url, tls_config, response_timeout_ms, )?, @@ -384,7 +385,10 @@ impl CliCommands { mod tests { use super::{InputSourcePair, get_input_sources}; use crate::test_helper::MOCKALL_CONTEXT_SYNC; - use common::{from_server_interface::FromServerSender, to_server_interface::ToServerReceiver}; + use common::{ + config::ServerUrl, from_server_interface::FromServerSender, + to_server_interface::ToServerReceiver, + }; use grpc::security::TLSConfig; use std::collections::VecDeque; @@ -397,7 +401,7 @@ mod tests { mockall::mock! { pub GRPCCommunicationsClient { - pub fn new_cli_communication(name: String, server_address: String, tls_config: Option) -> Self; + pub fn new_cli_communication(name: String, server_address: ServerUrl, tls_config: Option) -> Self; pub async fn run( &mut self, mut server_rx: ToServerReceiver, diff --git a/ank/src/cli_commands/server_connection.rs b/ank/src/cli_commands/server_connection.rs index 9dac62268..d2dab4e78 100644 --- a/ank/src/cli_commands/server_connection.rs +++ b/ank/src/cli_commands/server_connection.rs @@ -25,6 +25,7 @@ use common::{ commands::UpdateWorkloadState, communications_client::CommunicationsClient, communications_error::CommunicationMiddlewareError, + config::ServerUrl, from_server_interface::{FromServer, FromServerReceiver}, to_server_interface::{ToServer, ToServerInterface, ToServerSender}, }; @@ -111,7 +112,7 @@ impl ServerConnection { #[cfg_attr(test, allow(dead_code))] pub fn new( cli_name: &str, - server_url: String, + server_url: ServerUrl, tls_config: Option, response_timeout_ms: u64, ) -> Result { diff --git a/ank/src/main.rs b/ank/src/main.rs index e6a2d2686..5b7fcc79d 100644 --- a/ank/src/main.rs +++ b/ank/src/main.rs @@ -38,8 +38,12 @@ async fn main() { let config_file_paths = get_config_file_paths(); let config_file_paths_refs: Vec<&str> = config_file_paths.iter().map(|s| s.as_str()).collect(); let mut ank_config: AnkConfig = handle_config(&args.config_path, &config_file_paths_refs) - .unwrap_or_exit_func(|err| output_and_error!("Config file could not be parsed: {}", err), -1); - ank_config.update_with_args(&args) + .unwrap_or_exit_func( + |err| output_and_error!("Config file could not be parsed: {}", err), + -1, + ); + ank_config + .update_with_args(&args) .unwrap_or_exit_func(|err| output_and_error!("{}", err), -1); let cli_name = "ank-cli"; @@ -53,7 +57,7 @@ async fn main() { ank_config ); - let tls_config = if ank_config.address.starts_with("unix://") { + let tls_config = if ank_config.server_url.is_unix() { None } else { if let Err(err_message) = TLSConfig::is_config_conflicting( @@ -83,7 +87,7 @@ async fn main() { let mut cmd = CliCommands::init( ank_config.response_timeout, cli_name.to_string(), - ank_config.address.clone(), + ank_config.server_url, ank_config.no_wait, tls_config, ) diff --git a/common/src/config.rs b/common/src/config.rs index 48b6960ee..09173e9c8 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -12,9 +12,11 @@ // // SPDX-License-Identifier: Apache-2.0 +use serde::Deserialize; use std::fmt; use std::path::Path; use std::path::PathBuf; +use std::str::FromStr; use crate::DEFAULT_SERVER_ADDRESS; @@ -22,6 +24,10 @@ use crate::DEFAULT_SERVER_ADDRESS; pub const CONFIG_VERSION: &str = "v1"; +pub const UNIX_SOCKET_SCHEME: &str = "unix://"; +pub const HTTP_SCHEME: &str = "http://"; +pub const HTTPS_SCHEME: &str = "https://"; + #[derive(Debug, PartialEq, Eq, Clone)] pub enum ConversionErrors { WrongVersion(String), @@ -34,37 +40,28 @@ pub trait ConfigFile: Default + Sized { fn from_file(file_path: PathBuf) -> Result; } -pub fn get_default_server_url() -> String { - DEFAULT_SERVER_ADDRESS.to_string() +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(try_from = "String")] +pub enum ServerUrl { + Tcp(String), + Unix(PathBuf), } -pub fn validate_server_address_format(address: &str) -> Result<(), String> { - if let Some(path) = address.strip_prefix("unix://") { - if path.is_empty() { - return Err("Wrong server address format: empty unix socket path.".to_string()); - } - if !Path::new(path).is_absolute() { - return Err(format!( - "Wrong server address format: unix socket path must be absolute: '{address}'." - )); - } - return Ok(()); +impl ServerUrl { + pub fn is_unix(&self) -> bool { + matches!(self, ServerUrl::Unix(_)) } - if address.starts_with("https://") || address.starts_with("http://") { - Ok(()) - } else { - Err(format!("Wrong server address format: '{address}'.")) - } -} + pub fn validate_tls_settings( + &self, + config_name: &str, + insecure: bool, + has_tls_certificate_settings: bool, + ) -> Result<(), String> { + if !self.is_unix() { + return Ok(()); + } -pub fn validate_unix_socket_tls_settings( - config_name: &str, - address: &str, - insecure: bool, - has_tls_certificate_settings: bool, -) -> Result<(), String> { - if address.starts_with("unix://") { if insecure { return Err(format!( "Invalid {config_name} config: 'insecure' must not be enabled for unix:// endpoints" @@ -76,9 +73,58 @@ pub fn validate_unix_socket_tls_settings( "Invalid {config_name} config: TLS certificate settings are not allowed for unix:// endpoints" )); } + + Ok(()) + } +} + +pub fn parse_unix_socket_path(raw_path: &str) -> Result { + if raw_path.is_empty() { + return Err("Wrong server url format: empty unix socket path.".to_string()); + } + if !Path::new(raw_path).is_absolute() { + return Err(format!( + "Wrong server url format: unix socket path must be absolute: '{raw_path}'." + )); + } + Ok(PathBuf::from(raw_path)) +} + +impl FromStr for ServerUrl { + type Err = String; + + fn from_str(url: &str) -> Result { + if let Some(path) = url.strip_prefix(UNIX_SOCKET_SCHEME) { + return parse_unix_socket_path(path).map(ServerUrl::Unix); + } + + if url.starts_with(HTTPS_SCHEME) || url.starts_with(HTTP_SCHEME) { + Ok(ServerUrl::Tcp(url.to_string())) + } else { + Err(format!("Wrong server url format: '{url}'.")) + } } +} + +impl TryFrom for ServerUrl { + type Error = String; + + fn try_from(url: String) -> Result { + url.parse() + } +} + +impl fmt::Display for ServerUrl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ServerUrl::Tcp(url) => write!(f, "{url}"), + ServerUrl::Unix(path) => write!(f, "{UNIX_SOCKET_SCHEME}{}", path.display()), + } + } +} - Ok(()) +pub fn get_default_url() -> ServerUrl { + ServerUrl::Tcp(DEFAULT_SERVER_ADDRESS.to_string()) } impl fmt::Display for ConversionErrors { @@ -157,8 +203,8 @@ pub fn handle_config( #[cfg(test)] mod tests { use super::{ - ConfigFile, ConversionErrors, get_default_server_url, handle_config, - validate_server_address_format, validate_unix_socket_tls_settings, + ConfigFile, ConversionErrors, ServerUrl, UNIX_SOCKET_SCHEME, + get_default_url, handle_config, }; use crate::DEFAULT_SERVER_ADDRESS; use crate::std_extensions::UnreachableOption; @@ -196,54 +242,63 @@ mod tests { } #[test] - fn utest_get_default_server_url() { - assert_eq!(get_default_server_url(), DEFAULT_SERVER_ADDRESS.to_string()); + fn utest_get_default_url() { + assert_eq!( + get_default_url(), + ServerUrl::Tcp(DEFAULT_SERVER_ADDRESS.to_string()) + ); } #[test] - fn utest_validate_server_address_format_accepts_http_https_and_unix_absolute() { - assert!(validate_server_address_format("http://127.0.0.1:25551").is_ok()); - assert!(validate_server_address_format("https://127.0.0.1:25551").is_ok()); - assert!(validate_server_address_format("unix:///tmp/ank.sock").is_ok()); + fn utest_server_url_parses_http_https_and_unix_absolute() { + assert_eq!( + "http://127.0.0.1:25551".parse::(), + Ok(ServerUrl::Tcp("http://127.0.0.1:25551".to_string())) + ); + assert_eq!( + "https://127.0.0.1:25551".parse::(), + Ok(ServerUrl::Tcp("https://127.0.0.1:25551".to_string())) + ); + assert_eq!( + "unix:///tmp/ank.sock".parse::(), + Ok(ServerUrl::Unix(PathBuf::from("/tmp/ank.sock"))) + ); } #[test] - fn utest_validate_server_address_format_rejects_invalid_address() { - assert!(validate_server_address_format("127.0.0.1:25551").is_err()); + fn utest_server_url_rejects_invalid_url() { + assert!("127.0.0.1:25551".parse::().is_err()); } #[test] - fn utest_validate_server_address_format_rejects_empty_unix_path() { + fn utest_server_url_rejects_empty_unix_path() { assert_eq!( - validate_server_address_format("unix://"), - Err("Wrong server address format: empty unix socket path.".to_string()) + UNIX_SOCKET_SCHEME.parse::(), + Err("Wrong server url format: empty unix socket path.".to_string()) ); } #[test] - fn utest_validate_server_address_format_rejects_relative_unix_path() { - assert!(validate_server_address_format("unix://tmp/ank.sock").is_err()); + fn utest_server_url_rejects_relative_unix_path() { + assert!("unix://tmp/ank.sock".parse::().is_err()); } #[test] - fn utest_validate_unix_socket_tls_settings_allows_tcp_with_tls_or_insecure() { - assert!( - validate_unix_socket_tls_settings("ank", "https://127.0.0.1:25551", true, true).is_ok() - ); + fn utest_server_url_validate_tls_settings_allows_tcp_with_tls_or_insecure() { + let url = ServerUrl::Tcp("https://127.0.0.1:25551".to_string()); + assert!(url.validate_tls_settings("ank", true, true).is_ok()); } #[test] - fn utest_validate_unix_socket_tls_settings_rejects_unix_with_insecure() { - assert!( - validate_unix_socket_tls_settings("ank", "unix:///tmp/ank.sock", true, false).is_err() - ); + fn utest_server_url_validate_tls_settings_rejects_unix_with_insecure() { + let url = ServerUrl::Unix(PathBuf::from("/tmp/ank.sock")); + assert!(url.validate_tls_settings("ank", true, false).is_err()); } #[test] - fn utest_validate_unix_socket_tls_settings_rejects_unix_with_tls() { - assert!( - validate_unix_socket_tls_settings("ank", "unix:///tmp/ank.sock", false, true).is_err() - ); + fn utest_server_url_validate_tls_settings_rejects_unix_with_tls() { + let url = ServerUrl::Unix(PathBuf::from("/tmp/ank.sock")); + assert!(url.validate_tls_settings("ank", false, true).is_err()); } #[derive(Debug, Deserialize, PartialEq)] diff --git a/grpc/src/client.rs b/grpc/src/client.rs index db0566936..a64efc336 100644 --- a/grpc/src/client.rs +++ b/grpc/src/client.rs @@ -24,13 +24,13 @@ use crate::to_server_proxy::forward_from_ankaios_to_proto; use common::communications_client::CommunicationsClient; use common::communications_error::CommunicationMiddlewareError; +use common::config::ServerUrl; use common::from_server_interface::FromServerSender; use common::std_extensions::IllegalStateResult; use common::to_server_interface::ToServerReceiver; use async_trait::async_trait; use hyper_util::rt::TokioIo; -use regex::Regex; use std::collections::HashMap; use std::path::PathBuf; use tokio::net::UnixStream; @@ -47,59 +47,14 @@ enum ConnectionType { Cli, } -enum ServerEndpoint { - Tcp(String), - Unix(PathBuf), -} - pub struct GRPCCommunicationsClient { name: String, - server_endpoint: ServerEndpoint, + server_url: ServerUrl, connection_type: ConnectionType, tags: HashMap, tls_config: Option, } -fn get_server_url(server_address: &str, tls_config: &Option) -> String { - if tls_config.is_none() { - server_address.replace("https:", "http:") - } else { - server_address.to_owned() - } -} - -fn parse_server_endpoint( - server_address: &String, - tls_config: &Option, -) -> Result { - if let Some(path) = server_address.strip_prefix("unix://") { - if path.is_empty() { - return Err(CommunicationMiddlewareError( - "Wrong server address format: empty unix socket path.".to_string(), - )); - } - if tls_config.is_some() { - return Err(CommunicationMiddlewareError( - "Invalid server address and TLS configuration: unix:// endpoints do not support TLS settings." - .to_string(), - )); - } - return Ok(ServerEndpoint::Unix(PathBuf::from(path))); - } - - let re = Regex::new(r"^https?:\/\/.+").unwrap_or_illegal_state(); - if !re.is_match(server_address) { - return Err(CommunicationMiddlewareError(format!( - "Wrong server address format: '{server_address}'." - ))); - } - - Ok(ServerEndpoint::Tcp(get_server_url( - server_address, - tls_config, - ))) -} - fn build_client_tls_config(tls_config: &TLSConfig) -> ClientTlsConfig { // [impl->swdd~grpc-supports-pem-file-format-for-X509-certificates~1] let ca = Certificate::from_pem(&tls_config.ca_pem); @@ -116,15 +71,6 @@ fn build_client_tls_config(tls_config: &TLSConfig) -> ClientTlsConfig { .identity(client_identity) } -fn get_tcp_endpoint_for_tls(server_endpoint: &ServerEndpoint) -> Result<&str, GrpcMiddlewareError> { - match server_endpoint { - ServerEndpoint::Tcp(endpoint) => Ok(endpoint.as_str()), - ServerEndpoint::Unix(_) => Err(GrpcMiddlewareError::ConnectionInterrupted( - "TLS is not supported for unix:// endpoints".to_string(), - )), - } -} - async fn connect_unix_channel(path: PathBuf) -> Result { Endpoint::try_from("http://[::]:50061") .map_err(|err| GrpcMiddlewareError::ConnectionInterrupted(err.to_string()))? @@ -139,15 +85,13 @@ async fn connect_unix_channel(path: PathBuf) -> Result, tls_config: Option, ) -> Result { - let server_endpoint = parse_server_endpoint(&server_address, &tls_config)?; - Ok(Self { name, - server_endpoint, + server_url, connection_type: ConnectionType::Agent, tags, tls_config, @@ -156,14 +100,12 @@ impl GRPCCommunicationsClient { pub fn new_cli_communication( name: String, - server_address: String, + server_url: ServerUrl, tls_config: Option, ) -> Result { - let server_endpoint = parse_server_endpoint(&server_address, &tls_config)?; - Ok(Self { name, - server_endpoint, + server_url, connection_type: ConnectionType::Cli, tags: HashMap::new(), tls_config, @@ -205,7 +147,7 @@ impl CommunicationsClient for GRPCCommunicationsClient { log::debug!("No connection to the server: '{err}'"); return Err(CommunicationMiddlewareError(format!( "Could not connect to Ankaios server on '{}'.", - self.server_endpoint + self.server_url ))); } // [impl->swdd~grpc-client-outputs-error-server-connection-loss-for-cli-connection~1] @@ -297,13 +239,12 @@ impl GRPCCommunicationsClient { grpc_rx: Receiver, ) -> Result, GrpcMiddlewareError> { match self.connection_type { - ConnectionType::Agent => match &self.tls_config { + ConnectionType::Agent => match (&self.tls_config, &self.server_url) { // [impl->swdd~grpc-agent-activate-mtls-when-certificates-and-key-provided-upon-start~1] - Some(tls_config) => { + (Some(tls_config), ServerUrl::Tcp(endpoint)) => { let tls = build_client_tls_config(tls_config); - let tcp_endpoint = get_tcp_endpoint_for_tls(&self.server_endpoint)?; - let channel = Channel::from_shared(tcp_endpoint.to_string()) + let channel = Channel::from_shared(endpoint.to_string()) .map_err(|err| GrpcMiddlewareError::TLSError(err.to_string()))? .tls_config(tls)? .connect() @@ -316,17 +257,25 @@ impl GRPCCommunicationsClient { .into_inner(); Ok(res) } + (Some(_), ServerUrl::Unix(_)) => { + Err(GrpcMiddlewareError::ConnectionInterrupted( + "TLS is not supported for unix:// endpoints".to_string(), + )) + } // [impl->swdd~grpc-agent-deactivate-mtls-when-no-certificates-and-no-key-provided-upon-start~1] - None => { - let mut client = match &self.server_endpoint { - ServerEndpoint::Tcp(endpoint) => { - AgentConnectionClient::connect(endpoint.to_string()).await? - } - ServerEndpoint::Unix(path) => { - let channel = connect_unix_channel(path.clone()).await?; - AgentConnectionClient::new(channel) - } - }; + (None, ServerUrl::Tcp(endpoint)) => { + let endpoint = endpoint.replace("https:", "http:"); + let mut client = AgentConnectionClient::connect(endpoint).await?; + + let res = client + .connect_agent(ReceiverStream::new(grpc_rx)) + .await? + .into_inner(); + Ok(res) + } + (None, ServerUrl::Unix(path)) => { + let channel = connect_unix_channel(path.clone()).await?; + let mut client = AgentConnectionClient::new(channel); let res = client .connect_agent(ReceiverStream::new(grpc_rx)) @@ -335,13 +284,12 @@ impl GRPCCommunicationsClient { Ok(res) } }, - ConnectionType::Cli => match &self.tls_config { + ConnectionType::Cli => match (&self.tls_config, &self.server_url) { // [impl->swdd~grpc-cli-activate-mtls-when-certificates-and-key-provided-upon-start~1] - Some(tls_config) => { + (Some(tls_config), ServerUrl::Tcp(endpoint)) => { let tls = build_client_tls_config(tls_config); - let tcp_endpoint = get_tcp_endpoint_for_tls(&self.server_endpoint)?; - let channel = Channel::from_shared(tcp_endpoint.to_string()) + let channel = Channel::from_shared(endpoint.to_string()) .map_err(|err| GrpcMiddlewareError::TLSError(err.to_string()))? .tls_config(tls)? .connect() @@ -355,17 +303,15 @@ impl GRPCCommunicationsClient { .into_inner(); Ok(res) } + (Some(_), ServerUrl::Unix(_)) => { + Err(GrpcMiddlewareError::ConnectionInterrupted( + "TLS is not supported for unix:// endpoints".to_string(), + )) + } // [impl->swdd~grpc-cli-deactivate-mtls-when-no-certificates-and-no-key-provided-upon-start~1] - None => { - let mut client = match &self.server_endpoint { - ServerEndpoint::Tcp(endpoint) => { - CliConnectionClient::connect(endpoint.to_string()).await? - } - ServerEndpoint::Unix(path) => { - let channel = connect_unix_channel(path.clone()).await?; - CliConnectionClient::new(channel) - } - }; + (None, ServerUrl::Tcp(endpoint)) => { + let endpoint = endpoint.replace("https:", "http:"); + let mut client = CliConnectionClient::connect(endpoint).await?; #[allow(deprecated)] let res = client @@ -374,16 +320,18 @@ impl GRPCCommunicationsClient { .into_inner(); Ok(res) } - }, - } - } -} + (None, ServerUrl::Unix(path)) => { + let channel = connect_unix_channel(path.clone()).await?; + let mut client = CliConnectionClient::new(channel); -impl std::fmt::Display for ServerEndpoint { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ServerEndpoint::Tcp(address) => write!(f, "{address}"), - ServerEndpoint::Unix(path) => write!(f, "unix://{}", path.display()), + #[allow(deprecated)] + let res = client + .connect_cli(ReceiverStream::new(grpc_rx)) + .await? + .into_inner(); + Ok(res) + } + }, } } } diff --git a/grpc/tests/grpc_test.rs b/grpc/tests/grpc_test.rs index 5b30c00bb..0255dcf54 100644 --- a/grpc/tests/grpc_test.rs +++ b/grpc/tests/grpc_test.rs @@ -20,6 +20,7 @@ mod grpc_tests { communications_client::CommunicationsClient, communications_error::CommunicationMiddlewareError, communications_server::{CommunicationsServer, ServerConnection}, + config::ServerUrl, from_server_interface::{FromServer, FromServerSender}, to_server_interface::{ToServer, ToServerInterface, ToServerReceiver, ToServerSender}, }; @@ -224,7 +225,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb JoinHandle>, ) { let (to_grpc_client, grpc_client_receiver) = mpsc::channel::(20); - let url = format!("https://{server_addr}"); + let url = ServerUrl::Tcp(format!("https://{server_addr}")); let grpc_communications_client = match comm_type { CommunicationType::Cli => GRPCCommunicationsClient::new_cli_communication( test_request_id.to_owned(), diff --git a/server/src/server_config.rs b/server/src/server_config.rs index 6cc3a3dc7..b82b25ccf 100644 --- a/server/src/server_config.rs +++ b/server/src/server_config.rs @@ -15,7 +15,9 @@ use crate::cli::Arguments; use common::DEFAULT_SOCKET_ADDRESS; use common::communications_server::ServerConnection; -use common::config::{CONFIG_VERSION, ConfigFile, ConversionErrors}; +use common::config::{ + CONFIG_VERSION, ConfigFile, ConversionErrors, UNIX_SOCKET_SCHEME, parse_unix_socket_path, +}; use common::std_extensions::{UnreachableOption, UnreachableResult}; use grpc::security::PemFileType; @@ -28,7 +30,7 @@ use tests::read_pem_file; use serde::{Deserialize, Deserializer}; use std::fs::read_to_string; use std::net::SocketAddr; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use toml::from_str; pub const DEFAULT_SERVER_CONFIG_FILE_PATH: [&str; 1] = ["/etc/ankaios/ank-server.conf"]; @@ -45,16 +47,8 @@ fn get_default_mutating_hooks_path() -> PathBuf { fn parse_server_address(value: &str) -> Result { // [impl->swdd~server-uses-single-communication-listen-endpoint~1] // [impl->swdd~server-supports-unix-domain-socket-endpoints~1] - if let Some(path) = value.strip_prefix("unix://") { - if path.is_empty() { - return Err("Unix domain socket path cannot be empty".to_string()); - } - - if !Path::new(path).is_absolute() { - return Err(format!("Unix domain socket path '{path}' must be absolute")); - } - - return Ok(ServerConnection::Unix(PathBuf::from(path))); + if let Some(path) = value.strip_prefix(UNIX_SOCKET_SCHEME) { + return parse_unix_socket_path(path).map(ServerConnection::Unix); } value From f5531ba4e955907e04b99866844f4bb1a5098230 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 14:18:49 +0200 Subject: [PATCH 27/55] Do not hand arbitrary string to validate_tls_function --- agent/src/agent_config.rs | 22 ++++++++++++---------- ank/src/ank_config.rs | 26 +++++++++++++------------- common/src/config.rs | 20 ++++++++------------ 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/agent/src/agent_config.rs b/agent/src/agent_config.rs index 249db9821..e9b6deffc 100644 --- a/agent/src/agent_config.rs +++ b/agent/src/agent_config.rs @@ -170,16 +170,18 @@ impl AgentConfig { } fn validate_socket_configuration(agent_config: &AgentConfig) -> Result<(), String> { - agent_config.server_url.validate_tls_settings( - "agent", - agent_config.insecure, - agent_config.ca_pem.is_some() - || agent_config.crt_pem.is_some() - || agent_config.key_pem.is_some() - || agent_config.ca_pem_content.is_some() - || agent_config.crt_pem_content.is_some() - || agent_config.key_pem_content.is_some(), - ) + agent_config + .server_url + .validate_tls_settings( + agent_config.insecure, + agent_config.ca_pem.is_some() + || agent_config.crt_pem.is_some() + || agent_config.key_pem.is_some() + || agent_config.ca_pem_content.is_some() + || agent_config.crt_pem_content.is_some() + || agent_config.key_pem_content.is_some(), + ) + .map_err(|err| format!("Invalid agent config: {err}")) } ////////////////////////////////////////////////////////////////////////////// diff --git a/ank/src/ank_config.rs b/ank/src/ank_config.rs index 57901f1f5..61a212648 100644 --- a/ank/src/ank_config.rs +++ b/ank/src/ank_config.rs @@ -14,9 +14,7 @@ use crate::cli::AnkCli; use crate::output_warn; -use common::config::{ - CONFIG_VERSION, ConfigFile, ConversionErrors, ServerUrl, get_default_url, -}; +use common::config::{CONFIG_VERSION, ConfigFile, ConversionErrors, ServerUrl, get_default_url}; use common::std_extensions::UnreachableOption; use grpc::security::PemFileType; @@ -291,16 +289,18 @@ impl AnkConfig { } fn validate_socket_configuration(ank_config: &AnkConfig) -> Result<(), String> { - ank_config.server_url.validate_tls_settings( - "ank", - ank_config.insecure, - ank_config.ca_pem.is_some() - || ank_config.crt_pem.is_some() - || ank_config.key_pem.is_some() - || ank_config.ca_pem_content.is_some() - || ank_config.crt_pem_content.is_some() - || ank_config.key_pem_content.is_some(), - ) + ank_config + .server_url + .validate_tls_settings( + ank_config.insecure, + ank_config.ca_pem.is_some() + || ank_config.crt_pem.is_some() + || ank_config.key_pem.is_some() + || ank_config.ca_pem_content.is_some() + || ank_config.crt_pem_content.is_some() + || ank_config.key_pem_content.is_some(), + ) + .map_err(|err| format!("Invalid ank config: {err}")) } ////////////////////////////////////////////////////////////////////////////// diff --git a/common/src/config.rs b/common/src/config.rs index 09173e9c8..938d7bd3e 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -54,7 +54,6 @@ impl ServerUrl { pub fn validate_tls_settings( &self, - config_name: &str, insecure: bool, has_tls_certificate_settings: bool, ) -> Result<(), String> { @@ -63,15 +62,13 @@ impl ServerUrl { } if insecure { - return Err(format!( - "Invalid {config_name} config: 'insecure' must not be enabled for unix:// endpoints" - )); + return Err("'insecure' must not be enabled for unix:// endpoints".to_string()); } if has_tls_certificate_settings { - return Err(format!( - "Invalid {config_name} config: TLS certificate settings are not allowed for unix:// endpoints" - )); + return Err( + "TLS certificate settings are not allowed for unix:// endpoints".to_string(), + ); } Ok(()) @@ -203,8 +200,7 @@ pub fn handle_config( #[cfg(test)] mod tests { use super::{ - ConfigFile, ConversionErrors, ServerUrl, UNIX_SOCKET_SCHEME, - get_default_url, handle_config, + ConfigFile, ConversionErrors, ServerUrl, UNIX_SOCKET_SCHEME, get_default_url, handle_config, }; use crate::DEFAULT_SERVER_ADDRESS; use crate::std_extensions::UnreachableOption; @@ -286,19 +282,19 @@ mod tests { #[test] fn utest_server_url_validate_tls_settings_allows_tcp_with_tls_or_insecure() { let url = ServerUrl::Tcp("https://127.0.0.1:25551".to_string()); - assert!(url.validate_tls_settings("ank", true, true).is_ok()); + assert!(url.validate_tls_settings(true, true).is_ok()); } #[test] fn utest_server_url_validate_tls_settings_rejects_unix_with_insecure() { let url = ServerUrl::Unix(PathBuf::from("/tmp/ank.sock")); - assert!(url.validate_tls_settings("ank", true, false).is_err()); + assert!(url.validate_tls_settings(true, false).is_err()); } #[test] fn utest_server_url_validate_tls_settings_rejects_unix_with_tls() { let url = ServerUrl::Unix(PathBuf::from("/tmp/ank.sock")); - assert!(url.validate_tls_settings("ank", false, true).is_err()); + assert!(url.validate_tls_settings(false, true).is_err()); } #[derive(Debug, Deserialize, PartialEq)] From 8975257147e12e4d0160a1dfb03ddbeefdc17dc6 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:05:57 +0200 Subject: [PATCH 28/55] Add test for wrong scheme in server address --- common/src/config.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/src/config.rs b/common/src/config.rs index 938d7bd3e..c227202f0 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -266,6 +266,14 @@ mod tests { assert!("127.0.0.1:25551".parse::().is_err()); } + #[test] + fn utest_server_url_rejects_unknown_scheme() { + assert_eq!( + "ftp://127.0.0.1:25551".parse::(), + Err("Wrong server url format: 'ftp://127.0.0.1:25551'.".to_string()) + ); + } + #[test] fn utest_server_url_rejects_empty_unix_path() { assert_eq!( From 013074744f88d6973c10a039ec6192379bc9b03c Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:26:23 +0200 Subject: [PATCH 29/55] Use `insecure = false` in example, as it was before --- doc/docs/reference/config-files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docs/reference/config-files.md b/doc/docs/reference/config-files.md index af1620ab1..0d7495071 100644 --- a/doc/docs/reference/config-files.md +++ b/doc/docs/reference/config-files.md @@ -50,7 +50,7 @@ socket_group = 'ankaios' # the Ankaios server, agents and the ank CLI. # If set to 'true' and the certificates are not provided, then the server shall not use TLS. # This option must not be used with 'unix://' addresses. -# insecure = true +# insecure = false # The path to ca certificate pem file. # TLS options are only valid with TCP addresses. From 6d2a803dec61ebde8f71be3708830f3e787f5938 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:28:13 +0200 Subject: [PATCH 30/55] Removed unneeded configuration from Network Setup page --- doc/docs/usage/network-setup.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/docs/usage/network-setup.md b/doc/docs/usage/network-setup.md index 144168412..f6a13a60a 100644 --- a/doc/docs/usage/network-setup.md +++ b/doc/docs/usage/network-setup.md @@ -213,7 +213,6 @@ Similarly, switch `/etc/ankaios/ank-agent.conf` to the network address of the se ```toml address = 'https://127.0.0.1:25551' -insecure = false ``` For the agent add the following lines to `/etc/ankaios/ank-agent.conf`: From fabda663c1fc615640e909677e6a07787299d30e Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:46:06 +0200 Subject: [PATCH 31/55] Improve name for Unix socket endpoint Co-authored-by: Kaloyan <36224699+krucod3@users.noreply.github.com> --- grpc/src/client.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/grpc/src/client.rs b/grpc/src/client.rs index a64efc336..0711e2fed 100644 --- a/grpc/src/client.rs +++ b/grpc/src/client.rs @@ -72,8 +72,10 @@ fn build_client_tls_config(tls_config: &TLSConfig) -> ClientTlsConfig { } async fn connect_unix_channel(path: PathBuf) -> Result { - Endpoint::try_from("http://[::]:50061") - .map_err(|err| GrpcMiddlewareError::ConnectionInterrupted(err.to_string()))? + // Tonic requires an Endpoint URI even when the transport is + // provided by a custom connector. The URI is not used for + // establishing the connection; UnixStream::connect() below is. + Endpoint::from_static("http://unix-socket") .connect_with_connector(service_fn(move |_| { let path = path.clone(); async move { UnixStream::connect(path).await.map(TokioIo::new) } From a79d1a6d0704c590ee2357f52adfbba1be30a163 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:57:15 +0200 Subject: [PATCH 32/55] Improve name on requirements for Unix sockets --- grpc/doc/swdesign/README.md | 6 +++--- grpc/src/client.rs | 22 +++++++++------------- grpc/src/server.rs | 6 +++--- grpc/src/server_unix_listener.rs | 10 +++++----- grpc/tests/grpc_test.rs | 2 +- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/grpc/doc/swdesign/README.md b/grpc/doc/swdesign/README.md index f116844a3..9b1ec3704 100644 --- a/grpc/doc/swdesign/README.md +++ b/grpc/doc/swdesign/README.md @@ -203,15 +203,15 @@ Needs: - impl - itest -#### gRPC Server supports unix domain socket endpoints -`swdd~grpc-server-supports-unix-domain-socket-endpoints~1` +#### gRPC Server supports TCP and unix domain socket endpoints +`swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1` Status: approved The gRPC Server shall support listening for incoming gRPC connections on either a TCP socket endpoint or a Unix domain socket endpoint. Rationale: -Unix domain sockets allow local communication without exposing a TCP port. +Unix domain sockets allow securing local communication using IAM improving the ease of use. Tags: - gRPC_Server diff --git a/grpc/src/client.rs b/grpc/src/client.rs index 0711e2fed..398017a3b 100644 --- a/grpc/src/client.rs +++ b/grpc/src/client.rs @@ -73,9 +73,9 @@ fn build_client_tls_config(tls_config: &TLSConfig) -> ClientTlsConfig { async fn connect_unix_channel(path: PathBuf) -> Result { // Tonic requires an Endpoint URI even when the transport is - // provided by a custom connector. The URI is not used for - // establishing the connection; UnixStream::connect() below is. - Endpoint::from_static("http://unix-socket") + // provided by a custom connector. The URI is not used for + // establishing the connection; UnixStream::connect() below is. + Endpoint::from_static("http://unix-socket") .connect_with_connector(service_fn(move |_| { let path = path.clone(); async move { UnixStream::connect(path).await.map(TokioIo::new) } @@ -259,11 +259,9 @@ impl GRPCCommunicationsClient { .into_inner(); Ok(res) } - (Some(_), ServerUrl::Unix(_)) => { - Err(GrpcMiddlewareError::ConnectionInterrupted( - "TLS is not supported for unix:// endpoints".to_string(), - )) - } + (Some(_), ServerUrl::Unix(_)) => Err(GrpcMiddlewareError::ConnectionInterrupted( + "TLS is not supported for unix:// endpoints".to_string(), + )), // [impl->swdd~grpc-agent-deactivate-mtls-when-no-certificates-and-no-key-provided-upon-start~1] (None, ServerUrl::Tcp(endpoint)) => { let endpoint = endpoint.replace("https:", "http:"); @@ -305,11 +303,9 @@ impl GRPCCommunicationsClient { .into_inner(); Ok(res) } - (Some(_), ServerUrl::Unix(_)) => { - Err(GrpcMiddlewareError::ConnectionInterrupted( - "TLS is not supported for unix:// endpoints".to_string(), - )) - } + (Some(_), ServerUrl::Unix(_)) => Err(GrpcMiddlewareError::ConnectionInterrupted( + "TLS is not supported for unix:// endpoints".to_string(), + )), // [impl->swdd~grpc-cli-deactivate-mtls-when-no-certificates-and-no-key-provided-upon-start~1] (None, ServerUrl::Tcp(endpoint)) => { let endpoint = endpoint.replace("https:", "http:"); diff --git a/grpc/src/server.rs b/grpc/src/server.rs index 40f10e626..51b850ba9 100644 --- a/grpc/src/server.rs +++ b/grpc/src/server.rs @@ -49,7 +49,7 @@ impl CommunicationsServer for GRPCCommunicationsServer { mut receiver: FromServerReceiver, addr: ServerConnection, ) -> Result<(), CommunicationMiddlewareError> { - // [impl->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [impl->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] // [impl->swdd~grpc-server-creates-agent-connection~1] let my_connection = GRPCAgentConnection::new(self.agent_senders.clone(), self.sender.clone()); @@ -104,7 +104,7 @@ impl CommunicationsServer for GRPCCommunicationsServer { } } } - // [impl->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [impl->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] (Some(_), ServerConnection::Unix(_)) => Err(CommunicationMiddlewareError( "Invalid runtime config: TLS is not supported for unix:// endpoints".to_string(), ))?, @@ -140,7 +140,7 @@ impl CommunicationsServer for GRPCCommunicationsServer { } } - // [impl->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [impl->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] (None, ServerConnection::Unix(unix_socket_path)) => { let listener = prepare_unix_listener(&unix_socket_path, self.unix_socket_group.as_deref())?; diff --git a/grpc/src/server_unix_listener.rs b/grpc/src/server_unix_listener.rs index 64d7b5279..e26f533c3 100644 --- a/grpc/src/server_unix_listener.rs +++ b/grpc/src/server_unix_listener.rs @@ -138,7 +138,7 @@ mod tests { PathBuf::from(shim::SHARED_SOCKET_PATH) } - // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_skips_existing_path_checks_when_path_is_missing() { shim::reset_overrides(); @@ -152,7 +152,7 @@ mod tests { shim::reset_overrides(); } - // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_replaces_stale_socket_file() { shim::reset_overrides(); @@ -168,7 +168,7 @@ mod tests { shim::reset_overrides(); } - // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_fails_when_removing_stale_socket_fails() { shim::reset_overrides(); @@ -193,7 +193,7 @@ mod tests { shim::reset_overrides(); } - // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_fails_if_path_exists_and_is_not_socket() { shim::reset_overrides(); @@ -216,7 +216,7 @@ mod tests { shim::reset_overrides(); } - // [utest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] fn utest_prepare_unix_listener_fails_when_existing_path_metadata_fails() { shim::reset_overrides(); diff --git a/grpc/tests/grpc_test.rs b/grpc/tests/grpc_test.rs index 0255dcf54..b6f901318 100644 --- a/grpc/tests/grpc_test.rs +++ b/grpc/tests/grpc_test.rs @@ -671,7 +671,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb ); } - // [itest->swdd~grpc-server-supports-unix-domain-socket-endpoints~1] + // [itest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] // [itest->swdd~server-supports-unix-domain-socket-endpoints~1] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn itest_grpc_communication_command_connection_over_unix_domain_socket() { From ac84a1e633b6a8f791ada1bafe04f3e218173fc7 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:00:57 +0200 Subject: [PATCH 33/55] Add more tracing for unix socket implementation --- grpc/src/server_unix_listener.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/grpc/src/server_unix_listener.rs b/grpc/src/server_unix_listener.rs index e26f533c3..c0310407d 100644 --- a/grpc/src/server_unix_listener.rs +++ b/grpc/src/server_unix_listener.rs @@ -27,6 +27,7 @@ use std::os::unix::fs::PermissionsExt; use std::path::Path; use tokio::net::UnixListener; +// [impl->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] pub(crate) fn prepare_unix_listener( socket_path: &Path, socket_group: Option<&str>, From 24369fcdea7df4edeef406719d28b1c2210abc6f Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:32:18 +0200 Subject: [PATCH 34/55] Add requirements for grpc client code supporting UDS --- grpc/doc/swdesign/README.md | 17 ++++++++++++++ grpc/src/client.rs | 19 +++++++++++----- grpc/tests/grpc_test.rs | 45 ++++++++++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/grpc/doc/swdesign/README.md b/grpc/doc/swdesign/README.md index 9b1ec3704..f53c002c0 100644 --- a/grpc/doc/swdesign/README.md +++ b/grpc/doc/swdesign/README.md @@ -320,6 +320,23 @@ Needs: - impl - itest +#### gRPC Client supports TCP and unix domain socket endpoints +`swdd~grpc-client-supports-tcp-and-unix-domain-socket-endpoints~1` + +Status: approved + +The gRPC Client shall support connecting to the gRPC Server via either a TCP socket endpoint or a Unix domain socket endpoint. + +Rationale: +Unix domain sockets allow securing local communication using IAM improving the ease of use. + +Tags: +- gRPC_Client + +Needs: +- impl +- itest + #### gRPC Client connects with agent hello `swdd~grpc-client-connects-with-agent-hello~1` diff --git a/grpc/src/client.rs b/grpc/src/client.rs index 398017a3b..2d1b78874 100644 --- a/grpc/src/client.rs +++ b/grpc/src/client.rs @@ -71,6 +71,7 @@ fn build_client_tls_config(tls_config: &TLSConfig) -> ClientTlsConfig { .identity(client_identity) } +// [impl->swdd~grpc-client-supports-tcp-and-unix-domain-socket-endpoints~1] async fn connect_unix_channel(path: PathBuf) -> Result { // Tonic requires an Endpoint URI even when the transport is // provided by a custom connector. The URI is not used for @@ -259,9 +260,11 @@ impl GRPCCommunicationsClient { .into_inner(); Ok(res) } - (Some(_), ServerUrl::Unix(_)) => Err(GrpcMiddlewareError::ConnectionInterrupted( - "TLS is not supported for unix:// endpoints".to_string(), - )), + (Some(_), ServerUrl::Unix(_)) => { + Err(GrpcMiddlewareError::ConnectionInterrupted( + "TLS is not supported for unix:// endpoints".to_string(), + )) + } // [impl->swdd~grpc-agent-deactivate-mtls-when-no-certificates-and-no-key-provided-upon-start~1] (None, ServerUrl::Tcp(endpoint)) => { let endpoint = endpoint.replace("https:", "http:"); @@ -273,6 +276,7 @@ impl GRPCCommunicationsClient { .into_inner(); Ok(res) } + // [impl->swdd~grpc-client-supports-tcp-and-unix-domain-socket-endpoints~1] (None, ServerUrl::Unix(path)) => { let channel = connect_unix_channel(path.clone()).await?; let mut client = AgentConnectionClient::new(channel); @@ -303,9 +307,11 @@ impl GRPCCommunicationsClient { .into_inner(); Ok(res) } - (Some(_), ServerUrl::Unix(_)) => Err(GrpcMiddlewareError::ConnectionInterrupted( - "TLS is not supported for unix:// endpoints".to_string(), - )), + (Some(_), ServerUrl::Unix(_)) => { + Err(GrpcMiddlewareError::ConnectionInterrupted( + "TLS is not supported for unix:// endpoints".to_string(), + )) + } // [impl->swdd~grpc-cli-deactivate-mtls-when-no-certificates-and-no-key-provided-upon-start~1] (None, ServerUrl::Tcp(endpoint)) => { let endpoint = endpoint.replace("https:", "http:"); @@ -318,6 +324,7 @@ impl GRPCCommunicationsClient { .into_inner(); Ok(res) } + // [impl->swdd~grpc-client-supports-tcp-and-unix-domain-socket-endpoints~1] (None, ServerUrl::Unix(path)) => { let channel = connect_unix_channel(path.clone()).await?; let mut client = CliConnectionClient::new(channel); diff --git a/grpc/tests/grpc_test.rs b/grpc/tests/grpc_test.rs index b6f901318..163c39b8f 100644 --- a/grpc/tests/grpc_test.rs +++ b/grpc/tests/grpc_test.rs @@ -495,6 +495,49 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb ); } + // [itest->swdd~grpc-client-supports-tcp-and-unix-domain-socket-endpoints~1] + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + async fn itest_grpc_communication_client_agent_connection_over_unix_domain_socket() { + let test_agent_name = "test_agent_name"; + let tempdir = TempDir::new().unwrap(); + let socket_path = tempdir.path().join("ankaios.sock"); + + let (to_grpc_server, grpc_server_receiver) = mpsc::channel::(20); + let (to_server, mut server_receiver) = mpsc::channel::(20); + + let mut communications_server = GRPCCommunicationsServer::new(to_server, None); + let socket_endpoint = ServerConnection::Unix(socket_path.clone()); + let _grpc_server_task = tokio::spawn(async move { + communications_server + .start(grpc_server_receiver, socket_endpoint) + .await + }); + + let (_, grpc_client_receiver) = mpsc::channel::(20); + let url = ServerUrl::Unix(socket_path); + let grpc_communications_client = GRPCCommunicationsClient::new_agent_communication( + test_agent_name.to_owned(), + url, + HashMap::new(), + None, + ); + let _grpc_client_task = tokio::spawn(async move { + grpc_communications_client? + .run(grpc_client_receiver, to_grpc_server) + .await + }); + + let result = timeout(Duration::from_secs(10), server_receiver.recv()).await; + + assert_eq!( + result, + Ok(Some(ToServer::AgentHello(commands::AgentHello { + agent_name: test_agent_name.to_owned(), + tags: Default::default(), + }))) + ); + } + // Starts an insecure gRPC communication server for testing the commander connection // and returns the receiver on which the server forwards the received ToServer messages. // The returned FromServerSender must be kept alive for the whole test, otherwise the @@ -677,7 +720,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb async fn itest_grpc_communication_command_connection_over_unix_domain_socket() { let test_request_id = "test_request_id"; let tempdir = TempDir::new().unwrap(); - let socket_path = tempdir.path().join("ankaios-grpc.sock"); + let socket_path = tempdir.path().join("ankaios.sock"); let (to_grpc_server, grpc_server_receiver) = mpsc::channel::(20); let (to_server, mut server_receiver) = mpsc::channel::(20); From e2962215a76b21b45a5146326c71c2079430e7a0 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:06:45 +0200 Subject: [PATCH 35/55] Improve requirements and tracing of socket access rights --- grpc/doc/swdesign/README.md | 34 ++++++++++++++ grpc/src/server.rs | 2 +- grpc/src/server_unix_listener.rs | 77 ++++++++++++++++++++++---------- grpc/tests/grpc_test.rs | 7 ++- server/doc/swdesign/README.md | 18 ++++++++ server/src/main.rs | 2 + 6 files changed, 115 insertions(+), 25 deletions(-) diff --git a/grpc/doc/swdesign/README.md b/grpc/doc/swdesign/README.md index f53c002c0..a8fa03f0a 100644 --- a/grpc/doc/swdesign/README.md +++ b/grpc/doc/swdesign/README.md @@ -220,6 +220,40 @@ Needs: - impl - itest +#### gRPC Server sets unix domain socket group ownership +`swdd~grpc-server-sets-unix-domain-socket-group-ownership~1` + +Status: approved + +When the gRPC Server creates a Unix domain socket endpoint and a socket group is configured, the gRPC Server shall set the group ownership of the socket file to that group. + +Rationale: +Group ownership of the socket file allows controlling local access to the Unix domain socket through group permissions. + +Tags: +- gRPC_Server + +Needs: +- impl +- utest + +#### gRPC Server restricts unix domain socket file permissions +`swdd~grpc-server-restricts-unix-domain-socket-permissions~1` + +Status: approved + +When the gRPC Server creates a Unix domain socket endpoint, the gRPC Server shall restrict the permissions of the socket file to `0660`. + +Rationale: +Restricting the socket file permissions to owner and group read/write access prevents other local users from connecting to the Unix domain socket. + +Tags: +- gRPC_Server + +Needs: +- impl +- utest + #### gRPC Server provides a gRPC endpoint for CLI connection handling `swdd~grpc-server-provides-endpoint-for-cli-connection-handling~1` diff --git a/grpc/src/server.rs b/grpc/src/server.rs index 51b850ba9..197deba94 100644 --- a/grpc/src/server.rs +++ b/grpc/src/server.rs @@ -141,6 +141,7 @@ impl CommunicationsServer for GRPCCommunicationsServer { } } // [impl->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] + // [impl->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] (None, ServerConnection::Unix(unix_socket_path)) => { let listener = prepare_unix_listener(&unix_socket_path, self.unix_socket_group.as_deref())?; @@ -184,7 +185,6 @@ impl GRPCCommunicationsServer { } pub fn with_unix_socket_group(mut self, unix_socket_group: Option) -> Self { - // [impl->swdd~server-configures-unix-domain-socket-group~1] self.unix_socket_group = unix_socket_group; self } diff --git a/grpc/src/server_unix_listener.rs b/grpc/src/server_unix_listener.rs index c0310407d..bc70ac69a 100644 --- a/grpc/src/server_unix_listener.rs +++ b/grpc/src/server_unix_listener.rs @@ -41,7 +41,7 @@ pub(crate) fn prepare_unix_listener( )) })?; - configure_socket_group(socket_path, socket_group)?; + secure_socket_file(socket_path, socket_group)?; Ok(listener) } @@ -78,12 +78,12 @@ fn prepare_socket_path( Ok(()) } -fn configure_socket_group( +fn secure_socket_file( socket_path: &Path, socket_group: Option<&str>, ) -> Result<(), CommunicationMiddlewareError> { if let Some(group_name) = socket_group { - // [impl->swdd~server-configures-unix-domain-socket-group~1] + // [impl->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] let group = Group::from_name(group_name) .map_err(|err| { CommunicationMiddlewareError(format!( @@ -105,15 +105,16 @@ fn configure_socket_group( socket_path.display() )) })?; - - fs::set_permissions(socket_path, fs::Permissions::from_mode(0o660)).map_err(|err| { - CommunicationMiddlewareError(format!( - "Could not set unix socket permissions on '{}': {err}", - socket_path.display() - )) - })?; } + // [impl->swdd~grpc-server-restricts-unix-domain-socket-permissions~1] + fs::set_permissions(socket_path, fs::Permissions::from_mode(0o660)).map_err(|err| { + CommunicationMiddlewareError(format!( + "Could not set unix socket permissions on '{}': {err}", + socket_path.display() + )) + })?; + Ok(()) } @@ -128,7 +129,7 @@ fn configure_socket_group( #[cfg(test)] mod tests { use self::shim::{GROUP_GID, GROUP_NAME}; - use super::configure_socket_group; + use super::secure_socket_file; use super::prepare_socket_path; use nix::errno::Errno; use nix::unistd::Gid; @@ -244,29 +245,57 @@ mod tests { shim::reset_overrides(); } - // [utest->swdd~server-configures-unix-domain-socket-group~1] + // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] + // [utest->swdd~grpc-server-restricts-unix-domain-socket-permissions~1] #[test] fn utest_prepare_unix_listener_skips_group_configuration_without_group() { shim::reset_overrides(); let socket_path = socket_path(); + shim::expect_set_permissions_once(&socket_path, Ok(())); - let result = configure_socket_group(&socket_path, None); + let result = secure_socket_file(&socket_path, None); assert!(result.is_ok()); - shim::assert_calls(&[]); + shim::assert_calls(&["set_permissions"]); + + shim::reset_overrides(); + } + + // [utest->swdd~grpc-server-restricts-unix-domain-socket-permissions~1] + #[test] + fn utest_prepare_unix_listener_fails_when_setting_permissions_fails_without_group() { + shim::reset_overrides(); + + let socket_path = socket_path(); + shim::expect_set_permissions_once( + &socket_path, + Err(io::Error::other("set permissions failed")), + ); + + let result = secure_socket_file(&socket_path, None); + + assert!(result.is_err()); + assert!( + result + .err() + .unwrap() + .0 + .contains("Could not set unix socket permissions") + ); + shim::assert_calls(&["set_permissions"]); shim::reset_overrides(); } - // [utest->swdd~server-configures-unix-domain-socket-group~1] + // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] #[test] fn utest_prepare_unix_listener_fails_for_unknown_group() { shim::reset_overrides(); let socket_path = socket_path(); shim::expect_group_from_name_once(GROUP_NAME, Ok(None)); - let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + let result = secure_socket_file(&socket_path, Some(GROUP_NAME)); assert!(result.is_err()); assert!(result.err().unwrap().0.contains("group does not exist")); @@ -275,7 +304,8 @@ mod tests { shim::reset_overrides(); } - // [utest->swdd~server-configures-unix-domain-socket-group~1] + // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] + // [utest->swdd~grpc-server-restricts-unix-domain-socket-permissions~1] #[test] fn utest_prepare_unix_listener_configures_group_permissions() { shim::reset_overrides(); @@ -287,7 +317,7 @@ mod tests { shim::expect_chown_once(&socket_path, gid, Ok(())); shim::expect_set_permissions_once(&socket_path, Ok(())); - let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + let result = secure_socket_file(&socket_path, Some(GROUP_NAME)); assert!(result.is_ok()); shim::assert_calls(&["group_from_name", "chown", "set_permissions"]); @@ -295,7 +325,7 @@ mod tests { shim::reset_overrides(); } - // [utest->swdd~server-configures-unix-domain-socket-group~1] + // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] #[test] fn utest_prepare_unix_listener_fails_when_chown_fails() { shim::reset_overrides(); @@ -306,7 +336,7 @@ mod tests { shim::expect_group_from_name_once(GROUP_NAME, Ok(Some(shim::Group { gid }))); shim::expect_chown_once(&socket_path, gid, Err(Errno::EPERM)); - let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + let result = secure_socket_file(&socket_path, Some(GROUP_NAME)); assert!(result.is_err()); assert!( @@ -321,13 +351,14 @@ mod tests { shim::reset_overrides(); } + // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] #[test] fn utest_prepare_unix_listener_fails_when_group_lookup_fails() { shim::reset_overrides(); let socket_path = socket_path(); shim::expect_group_from_name_once(GROUP_NAME, Err(Errno::EINVAL)); - let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + let result = secure_socket_file(&socket_path, Some(GROUP_NAME)); assert!(result.is_err()); assert!( @@ -342,7 +373,7 @@ mod tests { shim::reset_overrides(); } - // [utest->swdd~server-configures-unix-domain-socket-group~1] + // [utest->swdd~grpc-server-restricts-unix-domain-socket-permissions~1] #[test] fn utest_prepare_unix_listener_fails_when_setting_permissions_fails() { shim::reset_overrides(); @@ -357,7 +388,7 @@ mod tests { Err(io::Error::other("set permissions failed")), ); - let result = configure_socket_group(&socket_path, Some(GROUP_NAME)); + let result = secure_socket_file(&socket_path, Some(GROUP_NAME)); assert!(result.is_err()); assert!( diff --git a/grpc/tests/grpc_test.rs b/grpc/tests/grpc_test.rs index 163c39b8f..24e92c9fe 100644 --- a/grpc/tests/grpc_test.rs +++ b/grpc/tests/grpc_test.rs @@ -716,6 +716,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb // [itest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] // [itest->swdd~server-supports-unix-domain-socket-endpoints~1] + // [itest->swdd~server-restricts-unix-domain-socket-permissions~1] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn itest_grpc_communication_command_connection_over_unix_domain_socket() { let test_request_id = "test_request_id"; @@ -743,7 +744,11 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb .await .unwrap(); - let mut client = connect_command_client_with_retry_unix_socket(socket_path).await; + let mut client = connect_command_client_with_retry_unix_socket(socket_path.clone()).await; + + let socket_permissions = std::fs::metadata(&socket_path).unwrap().permissions(); + assert_eq!(socket_permissions.mode() & 0o777, 0o660); + let _response_stream = client .connect_command(ReceiverStream::new(grpc_client_receiver)) .await diff --git a/server/doc/swdesign/README.md b/server/doc/swdesign/README.md index f0a433caa..393785ebb 100644 --- a/server/doc/swdesign/README.md +++ b/server/doc/swdesign/README.md @@ -423,6 +423,24 @@ Needs: - impl - utest +#### Server restricts unix domain socket file permissions +`swdd~server-restricts-unix-domain-socket-permissions~1` + +Status: approved + +When the Ankaios server is configured with a Unix domain socket endpoint, the server shall restrict the permissions of the created socket file to `0660` regardless of whether a `socket_group` is configured. + +Rationale: +Restricting the socket file permissions to owner and group read/write access prevents other local users from connecting to the Unix domain socket. + +Tags: +- AnkaiosServer +- Communication Middleware + +Needs: +- impl +- itest + #### Server uses common async communication channels `swdd~server-uses-async-channels~1` diff --git a/server/src/main.rs b/server/src/main.rs index 8d0ef71f7..a99437afd 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -134,6 +134,7 @@ async fn main() { ServerConnection::Unix(_) => None, }; + // [impl->swdd~server-configures-unix-domain-socket-group~1] let mut communications_server = GRPCCommunicationsServer::new(to_server, tls_config) .with_unix_socket_group(server_config.socket_group.clone()); @@ -146,6 +147,7 @@ async fn main() { tokio::select! { // [impl->swdd~server-default-communication-grpc~1] // [impl->swdd~server-supports-unix-domain-socket-endpoints~1] + // [impl->swdd~server-restricts-unix-domain-socket-permissions~1] communication_result = communications_server.start(agents_receiver, server_config.address) => { communication_result.unwrap_or_exit("server error") } From af8b74e2c2b34d3c59f1349a9a5a323b7280804a Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:40:59 +0200 Subject: [PATCH 36/55] Add needs utest Co-authored-by: Kaloyan <36224699+krucod3@users.noreply.github.com> --- grpc/doc/swdesign/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/grpc/doc/swdesign/README.md b/grpc/doc/swdesign/README.md index a8fa03f0a..3c97d3249 100644 --- a/grpc/doc/swdesign/README.md +++ b/grpc/doc/swdesign/README.md @@ -218,6 +218,7 @@ Tags: Needs: - impl +- utest - itest #### gRPC Server sets unix domain socket group ownership From cbdfb06af3e5bc23623a8951d65bb0b3933ca1df Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:45:26 +0200 Subject: [PATCH 37/55] Fix names of utests --- grpc/src/server_unix_listener.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/grpc/src/server_unix_listener.rs b/grpc/src/server_unix_listener.rs index bc70ac69a..10d3bfa0a 100644 --- a/grpc/src/server_unix_listener.rs +++ b/grpc/src/server_unix_listener.rs @@ -129,8 +129,8 @@ fn secure_socket_file( #[cfg(test)] mod tests { use self::shim::{GROUP_GID, GROUP_NAME}; - use super::secure_socket_file; use super::prepare_socket_path; + use super::secure_socket_file; use nix::errno::Errno; use nix::unistd::Gid; use std::io; @@ -142,7 +142,7 @@ mod tests { // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] - fn utest_prepare_unix_listener_skips_existing_path_checks_when_path_is_missing() { + fn utest_prepare_socket_path_skips_existing_path_checks_when_path_is_missing() { shim::reset_overrides(); let socket_path = socket_path(); @@ -156,7 +156,7 @@ mod tests { // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] - fn utest_prepare_unix_listener_replaces_stale_socket_file() { + fn utest_prepare_socket_path_replaces_stale_socket_file() { shim::reset_overrides(); let socket_path = socket_path(); @@ -172,7 +172,7 @@ mod tests { // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] - fn utest_prepare_unix_listener_fails_when_removing_stale_socket_fails() { + fn utest_prepare_socket_path_fails_when_removing_stale_socket_fails() { shim::reset_overrides(); let socket_path = socket_path(); @@ -197,7 +197,7 @@ mod tests { // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] - fn utest_prepare_unix_listener_fails_if_path_exists_and_is_not_socket() { + fn utest_prepare_socket_path_fails_if_path_exists_and_is_not_socket() { shim::reset_overrides(); let file_path = socket_path(); @@ -220,7 +220,7 @@ mod tests { // [utest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] - fn utest_prepare_unix_listener_fails_when_existing_path_metadata_fails() { + fn utest_prepare_socket_path_fails_when_existing_path_metadata_fails() { shim::reset_overrides(); let socket_path = socket_path(); @@ -248,7 +248,7 @@ mod tests { // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] // [utest->swdd~grpc-server-restricts-unix-domain-socket-permissions~1] #[test] - fn utest_prepare_unix_listener_skips_group_configuration_without_group() { + fn utest_secure_socket_file_skips_group_configuration_without_group() { shim::reset_overrides(); let socket_path = socket_path(); @@ -264,7 +264,7 @@ mod tests { // [utest->swdd~grpc-server-restricts-unix-domain-socket-permissions~1] #[test] - fn utest_prepare_unix_listener_fails_when_setting_permissions_fails_without_group() { + fn utest_secure_socket_file_fails_when_setting_permissions_fails_without_group() { shim::reset_overrides(); let socket_path = socket_path(); @@ -290,7 +290,7 @@ mod tests { // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] #[test] - fn utest_prepare_unix_listener_fails_for_unknown_group() { + fn utest_secure_socket_file_fails_for_unknown_group() { shim::reset_overrides(); let socket_path = socket_path(); shim::expect_group_from_name_once(GROUP_NAME, Ok(None)); @@ -307,7 +307,7 @@ mod tests { // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] // [utest->swdd~grpc-server-restricts-unix-domain-socket-permissions~1] #[test] - fn utest_prepare_unix_listener_configures_group_permissions() { + fn utest_secure_socket_file_configures_group_permissions() { shim::reset_overrides(); let socket_path = socket_path(); @@ -327,7 +327,7 @@ mod tests { // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] #[test] - fn utest_prepare_unix_listener_fails_when_chown_fails() { + fn utest_secure_socket_file_fails_when_chown_fails() { shim::reset_overrides(); let socket_path = socket_path(); @@ -353,7 +353,7 @@ mod tests { // [utest->swdd~grpc-server-sets-unix-domain-socket-group-ownership~1] #[test] - fn utest_prepare_unix_listener_fails_when_group_lookup_fails() { + fn utest_secure_socket_file_fails_when_group_lookup_fails() { shim::reset_overrides(); let socket_path = socket_path(); shim::expect_group_from_name_once(GROUP_NAME, Err(Errno::EINVAL)); @@ -375,7 +375,7 @@ mod tests { // [utest->swdd~grpc-server-restricts-unix-domain-socket-permissions~1] #[test] - fn utest_prepare_unix_listener_fails_when_setting_permissions_fails() { + fn utest_secure_socket_file_fails_when_setting_permissions_fails() { shim::reset_overrides(); let socket_path = socket_path(); From e4929899b57b104363b85642b2c806aa5aa2da28 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:07:01 +0200 Subject: [PATCH 38/55] Improve name of requirement --- grpc/tests/grpc_test.rs | 2 +- server/doc/swdesign/README.md | 4 ++-- server/src/main.rs | 2 +- server/src/server_config.rs | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/grpc/tests/grpc_test.rs b/grpc/tests/grpc_test.rs index 24e92c9fe..fc0eae8cb 100644 --- a/grpc/tests/grpc_test.rs +++ b/grpc/tests/grpc_test.rs @@ -715,7 +715,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb } // [itest->swdd~grpc-server-supports-tcp-and-unix-domain-socket-endpoints~1] - // [itest->swdd~server-supports-unix-domain-socket-endpoints~1] + // [itest->swdd~server-supports-tcp-and-unix-domain-socket-endpoints~1] // [itest->swdd~server-restricts-unix-domain-socket-permissions~1] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn itest_grpc_communication_command_connection_over_unix_domain_socket() { diff --git a/server/doc/swdesign/README.md b/server/doc/swdesign/README.md index 393785ebb..02eac9712 100644 --- a/server/doc/swdesign/README.md +++ b/server/doc/swdesign/README.md @@ -347,8 +347,8 @@ Needs: - impl - itest -#### Server supports unix domain socket endpoints -`swdd~server-supports-unix-domain-socket-endpoints~1` +#### Server supports TCP and unix domain socket endpoints +`swdd~server-supports-tcp-and-unix-domain-socket-endpoints~1` Status: approved diff --git a/server/src/main.rs b/server/src/main.rs index a99437afd..f0047ad38 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -146,7 +146,7 @@ async fn main() { tokio::select! { // [impl->swdd~server-default-communication-grpc~1] - // [impl->swdd~server-supports-unix-domain-socket-endpoints~1] + // [impl->swdd~server-supports-tcp-and-unix-domain-socket-endpoints~1] // [impl->swdd~server-restricts-unix-domain-socket-permissions~1] communication_result = communications_server.start(agents_receiver, server_config.address) => { communication_result.unwrap_or_exit("server error") diff --git a/server/src/server_config.rs b/server/src/server_config.rs index b82b25ccf..9834cb7ed 100644 --- a/server/src/server_config.rs +++ b/server/src/server_config.rs @@ -46,7 +46,7 @@ fn get_default_mutating_hooks_path() -> PathBuf { fn parse_server_address(value: &str) -> Result { // [impl->swdd~server-uses-single-communication-listen-endpoint~1] - // [impl->swdd~server-supports-unix-domain-socket-endpoints~1] + // [impl->swdd~server-supports-tcp-and-unix-domain-socket-endpoints~1] if let Some(path) = value.strip_prefix(UNIX_SOCKET_SCHEME) { return parse_unix_socket_path(path).map(ServerConnection::Unix); } @@ -172,7 +172,7 @@ impl ServerConfig { } if let Some(addr) = &args.addr { - // [impl->swdd~server-supports-unix-domain-socket-endpoints~1] + // [impl->swdd~server-supports-tcp-and-unix-domain-socket-endpoints~1] self.address = parse_server_address(addr)?; } @@ -635,7 +635,7 @@ mod tests { } // [utest->swdd~server-loads-config-file~2] - // [utest->swdd~server-supports-unix-domain-socket-endpoints~1] + // [utest->swdd~server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] fn utest_server_config_from_file_with_unix_domain_socket() { let server_config_content = r"# @@ -655,7 +655,7 @@ mod tests { } // [utest->swdd~server-loads-config-file~2] - // [utest->swdd~server-supports-unix-domain-socket-endpoints~1] + // [utest->swdd~server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] fn utest_server_config_update_with_args_unix_domain_socket() { let mut server_config = ServerConfig::default(); @@ -746,7 +746,7 @@ mod tests { assert_eq!(server_config.socket_group, Some("ankaios".to_string())); } - // [utest->swdd~server-supports-unix-domain-socket-endpoints~1] + // [utest->swdd~server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] fn utest_server_config_rejects_relative_unix_domain_socket_path_from_file() { let server_config_content = r"# @@ -762,7 +762,7 @@ mod tests { assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); } - // [utest->swdd~server-supports-unix-domain-socket-endpoints~1] + // [utest->swdd~server-supports-tcp-and-unix-domain-socket-endpoints~1] #[test] fn utest_server_config_rejects_relative_unix_domain_socket_path_from_args() { let mut server_config = ServerConfig::default(); From bb4928e0dff795dba64652fa12b25011b50cf650 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:21:36 +0200 Subject: [PATCH 39/55] Add requirement for valid socket configurations in agent and client --- agent/doc/swdesign/README.md | 20 ++++++++++++++++++++ agent/src/agent_config.rs | 3 +++ ank/doc/swdesign/README.md | 20 ++++++++++++++++++++ ank/src/ank_config.rs | 3 +++ 4 files changed, 46 insertions(+) diff --git a/agent/doc/swdesign/README.md b/agent/doc/swdesign/README.md index 598897887..8d7a9c838 100644 --- a/agent/doc/swdesign/README.md +++ b/agent/doc/swdesign/README.md @@ -455,6 +455,26 @@ Tags: Needs: - impl +#### Agent validates unix domain socket configuration +`swdd~agent-validates-unix-domain-socket-configuration~1` + +Status: approved + +When the Ankaios agent is configured with a Unix domain socket server url (`unix://`), the agent shall reject startup configuration that: + +- enables `insecure` +- sets TLS certificate paths or TLS certificate content fields + +Rationale: +Unix domain socket communication uses local filesystem permissions and does not require TLS transport settings. + +Tags: +- AgentManager + +Needs: +- impl +- utest + #### Agent uses common async communication channels `swdd~agent-uses-async-channels~1` diff --git a/agent/src/agent_config.rs b/agent/src/agent_config.rs index e9b6deffc..df7a54158 100644 --- a/agent/src/agent_config.rs +++ b/agent/src/agent_config.rs @@ -170,6 +170,7 @@ impl AgentConfig { } fn validate_socket_configuration(agent_config: &AgentConfig) -> Result<(), String> { + // [impl->swdd~agent-validates-unix-domain-socket-configuration~1] agent_config .server_url .validate_tls_settings( @@ -464,6 +465,7 @@ mod tests { assert!(!agent_config.tags.contains_key("config_tag")); } + // [utest->swdd~agent-validates-unix-domain-socket-configuration~1] #[test] fn utest_agent_config_rejects_insecure_with_unix_domain_socket() { let agent_config_content = r"# @@ -479,6 +481,7 @@ mod tests { assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); } + // [utest->swdd~agent-validates-unix-domain-socket-configuration~1] #[test] fn utest_agent_config_rejects_tls_with_unix_domain_socket() { let agent_config_content = r"# diff --git a/ank/doc/swdesign/README.md b/ank/doc/swdesign/README.md index fc20ee191..5dba6345c 100644 --- a/ank/doc/swdesign/README.md +++ b/ank/doc/swdesign/README.md @@ -307,6 +307,26 @@ Tags: Needs: - impl +#### CLI validates unix domain socket configuration +`swdd~cli-validates-unix-domain-socket-configuration~1` + +Status: approved + +When the Ankaios CLI is configured with a Unix domain socket server url (`unix://`), the CLI shall reject startup configuration that: + +- enables `insecure` +- sets TLS certificate paths or TLS certificate content fields + +Rationale: +Unix domain socket communication uses local filesystem permissions and does not require TLS transport settings. + +Tags: +- CliCommands + +Needs: +- impl +- utest + #### CLI supports cli argument for response timeout `swdd~cli-supports-cli-argument-for-response-timeout~1` diff --git a/ank/src/ank_config.rs b/ank/src/ank_config.rs index 61a212648..431692599 100644 --- a/ank/src/ank_config.rs +++ b/ank/src/ank_config.rs @@ -289,6 +289,7 @@ impl AnkConfig { } fn validate_socket_configuration(ank_config: &AnkConfig) -> Result<(), String> { + // [impl->swdd~cli-validates-unix-domain-socket-configuration~1] ank_config .server_url .validate_tls_settings( @@ -719,6 +720,7 @@ mod tests { } } + // [utest->swdd~cli-validates-unix-domain-socket-configuration~1] #[test] fn utest_ank_config_rejects_insecure_with_unix_domain_socket() { let ank_config_content = r"# @@ -735,6 +737,7 @@ mod tests { assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); } + // [utest->swdd~cli-validates-unix-domain-socket-configuration~1] #[test] fn utest_ank_config_rejects_tls_with_unix_domain_socket() { let ank_config_content = format!( From 09d4f26f51721f7bfb0920a8f7eeb751860a4abd Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:30:18 +0200 Subject: [PATCH 40/55] Add requirement and utest for empty group name not allowed --- server/doc/swdesign/README.md | 18 ++++++++++++++++++ server/src/server_config.rs | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/server/doc/swdesign/README.md b/server/doc/swdesign/README.md index 02eac9712..67458bf64 100644 --- a/server/doc/swdesign/README.md +++ b/server/doc/swdesign/README.md @@ -423,6 +423,24 @@ Needs: - impl - utest +#### Server rejects empty socket group +`swdd~server-rejects-empty-socket-group~1` + +Status: approved + +When the Ankaios server is configured with a Unix domain socket endpoint and a `socket_group` that is empty or consists only of whitespace, the server shall reject the startup configuration. + +Rationale: +An empty or whitespace-only `socket_group` does not identify a valid group and would otherwise be silently ignored, leading to confusing behavior. + +Tags: +- AnkaiosServer +- Communication Middleware + +Needs: +- impl +- utest + #### Server restricts unix domain socket file permissions `swdd~server-restricts-unix-domain-socket-permissions~1` diff --git a/server/src/server_config.rs b/server/src/server_config.rs index 9834cb7ed..390dda248 100644 --- a/server/src/server_config.rs +++ b/server/src/server_config.rs @@ -238,6 +238,7 @@ fn validate_socket_configuration(server_config: &ServerConfig) -> Result<(), Str if let Some(group) = server_config.socket_group.as_deref() && group.trim().is_empty() { + // [impl->swdd~server-rejects-empty-socket-group~1] return Err( "Invalid server config: 'socket_group' must not be empty when set".to_string(), ); @@ -712,6 +713,23 @@ mod tests { assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); } + // [utest->swdd~server-rejects-empty-socket-group~1] + #[test] + fn utest_server_config_rejects_empty_socket_group_with_unix_domain_socket() { + let server_config_content = r"# + version = 'v1' + address = 'unix:///tmp/ankaios-server.sock' + socket_group = ' ' + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{server_config_content}").unwrap(); + + let result = ServerConfig::from_file(PathBuf::from(tmp_config_file.path())); + + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } + // [utest->swdd~server-validates-socket-group-endpoint-compatibility~1] #[test] fn utest_server_config_rejects_socket_group_with_tcp_address() { From 9c0267485930c68740c52677f3a7911e04b02b7b Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:42:54 +0200 Subject: [PATCH 41/55] Add unit test for reject unix socket with ca_pem_content --- server/src/server_config.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/src/server_config.rs b/server/src/server_config.rs index 390dda248..2842b5560 100644 --- a/server/src/server_config.rs +++ b/server/src/server_config.rs @@ -713,6 +713,23 @@ mod tests { assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); } + // [utest->swdd~server-validates-unix-domain-socket-configuration~1] + #[test] + fn utest_server_config_rejects_tls_content_settings_with_unix_domain_socket() { + let server_config_content = r"# + version = 'v1' + address = 'unix:///tmp/ankaios-server.sock' + ca_pem_content = 'some ca pem content' + #"; + + let mut tmp_config_file = NamedTempFile::new().unwrap(); + write!(tmp_config_file, "{server_config_content}").unwrap(); + + let result = ServerConfig::from_file(PathBuf::from(tmp_config_file.path())); + + assert!(matches!(result, Err(ConversionErrors::InvalidConfig(_)))); + } + // [utest->swdd~server-rejects-empty-socket-group~1] #[test] fn utest_server_config_rejects_empty_socket_group_with_unix_domain_socket() { From e5172ad322daf87f3417fc719a3fcb4953e602ff Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:48:53 +0200 Subject: [PATCH 42/55] Change name constant for stest holding server URL --- tests/resources/ankaios.resource | 12 ++++++------ tests/resources/variables.resource | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/resources/ankaios.resource b/tests/resources/ankaios.resource index 45f85eef7..52decdc38 100644 --- a/tests/resources/ankaios.resource +++ b/tests/resources/ankaios.resource @@ -95,9 +95,9 @@ Clean up Ankaios Remove Environment Variable ANKSERVER_INSECURE Remove Environment Variable ANKAGENT_INSECURE Remove Environment Variable ANK_INSECURE - Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_URL} - Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_URL} - Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_URL} + Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_SOCKET_URL} + Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_SOCKET_URL} + Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_SOCKET_URL} Clean up Podman Log Clean up Podman @@ -131,9 +131,9 @@ Setup mTLS Set Environment Variable name=ANK_KEY_PEM value=${cli_key_pem} Setup Unix Socket - Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_URL} - Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_URL} - Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_URL} + Set Environment Variable name=ANKSERVER_SERVER_URL value=${ANKAIOS_SERVER_SOCKET_URL} + Set Environment Variable name=ANKAGENT_SERVER_URL value=${ANKAIOS_SERVER_SOCKET_URL} + Set Environment Variable name=ANK_SERVER_URL value=${ANKAIOS_SERVER_SOCKET_URL} Run in foreground diff --git a/tests/resources/variables.resource b/tests/resources/variables.resource index 7fa2887a7..583237e2c 100644 --- a/tests/resources/variables.resource +++ b/tests/resources/variables.resource @@ -18,6 +18,6 @@ ${CERTS_DIR}= /tmp/.certs ${AGENT_NAME}= agent_A ${AGENT_2_NAME}= agent_B ${ANKAIOS_TMP_FOLDER}= /tmp/ankaios -${ANKAIOS_SERVER_URL}= unix:///tmp/ankaios.sock +${ANKAIOS_SERVER_SOCKET_URL}= unix:///tmp/ankaios.sock ${ANKAIOS_SERVER_TCP_ADDRESS}= 127.0.0.1:25551 ${ANKAIOS_SERVER_HTTPS_URL}= https://127.0.0.1:25551 From d0bb3f01b87546038774cba1e270d66525054c30 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:55:54 +0200 Subject: [PATCH 43/55] Undo some more renames from URL to address --- agent/config/ank-agent.conf | 4 ++-- agent/src/cli.rs | 2 +- agent/src/main.rs | 2 +- ank/config/ank.conf | 4 ++-- ank/src/cli.rs | 6 +++--- ank/src/cli_commands.rs | 2 +- doc/docs/reference/config-files.md | 16 +++++++--------- doc/docs/usage/network-setup.md | 10 +++++----- tools/aur/README.md | 2 +- 9 files changed, 23 insertions(+), 25 deletions(-) diff --git a/agent/config/ank-agent.conf b/agent/config/ank-agent.conf index 37cfd049b..a4d1efc2d 100644 --- a/agent/config/ank-agent.conf +++ b/agent/config/ank-agent.conf @@ -10,8 +10,8 @@ version = 'v1' # (a-z and A-Z), numbers and the symbols "-" and "_". name = 'agent_A' -# The server endpoint. -address = 'unix:///run/ankaios.sock' +# The server url. +server_url = 'unix:///run/ankaios.sock' # An existing path where to manage the fifo files. # If not set, defaults to '$TMPDIR/ankaios/' (falls back to '/tmp/ankaios/' if TMPDIR is not set). diff --git a/agent/src/cli.rs b/agent/src/cli.rs index 379e4d699..fefdc6e32 100644 --- a/agent/src/cli.rs +++ b/agent/src/cli.rs @@ -37,7 +37,7 @@ pub struct Arguments { required = false, env = "ANKAGENT_SERVER_URL" )] - /// The server endpoint. + /// The server url. /// Supported values are https://host:port and unix:///path/to/socket. pub server_url: Option, #[arg(short = 'r', long = "run-folder", required = false)] diff --git a/agent/src/main.rs b/agent/src/main.rs index d4580b5b9..d8e53992b 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -132,7 +132,7 @@ async fn main() { .unwrap_or_exit("Error encountered while checking agent name!"); log::debug!( - "Starting the Ankaios agent with \n\tname: '{}', \n\tserver endpoint: '{}', \n\trun directory: '{}'", + "Starting the Ankaios agent with \n\tname: '{}', \n\tserver url: '{}', \n\trun directory: '{}'", agent_config.name, agent_config.server_url, agent_config.run_folder, diff --git a/ank/config/ank.conf b/ank/config/ank.conf index 0f4d3811b..2331ac900 100644 --- a/ank/config/ank.conf +++ b/ank/config/ank.conf @@ -17,10 +17,10 @@ version = 'v1' # no_wait = false [default] -# The endpoint to Ankaios server. +# The URL to Ankaios server. # If started in insecure mode then the HTTP protocol shall be used, # otherwise the HTTPS protocol shall be used. -address = 'unix:///run/ankaios.sock' +server_url = 'unix:///run/ankaios.sock' # The flag to disable TLS communication with the server. # If set to 'true', then the CLI shall not use TLS. diff --git a/ank/src/cli.rs b/ank/src/cli.rs index 381cc2613..35529639f 100644 --- a/ank/src/cli.rs +++ b/ank/src/cli.rs @@ -13,8 +13,8 @@ // SPDX-License-Identifier: Apache-2.0 use ankaios_api::ank_base::{ - AgentMap, CompleteState, ConfigMap, ExecutionsStatesOfWorkload, LogsRequest, - State, WorkloadInstanceName, WorkloadMap, WorkloadStatesMap, + AgentMap, CompleteState, ConfigMap, ExecutionsStatesOfWorkload, LogsRequest, State, + WorkloadInstanceName, WorkloadMap, WorkloadStatesMap, }; use common::helpers::parse_key_val; @@ -192,7 +192,7 @@ pub struct AnkCli { /// The default paths are $HOME/.config/ankaios/ank.conf and /etc/ankaios/ank.conf with first existing file taking precedence over the other. pub config_path: Option, #[arg(short = 's', long = "server-url", required=false, env = ANK_SERVER_URL_ENV_KEY)] - /// The endpoint to Ankaios server. + /// The url to Ankaios server. /// Supported values are https://host:port and unix:///path/to/socket. pub server_url: Option, #[arg( diff --git a/ank/src/cli_commands.rs b/ank/src/cli_commands.rs index 4d3e520e6..df4cf6e47 100644 --- a/ank/src/cli_commands.rs +++ b/ank/src/cli_commands.rs @@ -401,7 +401,7 @@ mod tests { mockall::mock! { pub GRPCCommunicationsClient { - pub fn new_cli_communication(name: String, server_address: ServerUrl, tls_config: Option) -> Self; + pub fn new_cli_communication(name: String, server_url: ServerUrl, tls_config: Option) -> Self; pub async fn run( &mut self, mut server_rx: ToServerReceiver, diff --git a/doc/docs/reference/config-files.md b/doc/docs/reference/config-files.md index 0d7495071..03b0b61e3 100644 --- a/doc/docs/reference/config-files.md +++ b/doc/docs/reference/config-files.md @@ -20,7 +20,7 @@ The Ankaios configuration files are per default loaded from the following locati The following three examples show how the Ankaios configuration files look like. By default, the standard [installation](../usage/installation.md) configures the server, agent and CLI to -communicate over a local Unix domain socket at `/run/ankaios/server.sock`, as shown in the examples +communicate over a local Unix domain socket at `/run/ankaios.sock`, as shown in the examples below. For communication over the network instead, see [Connecting Ankaios components over the network](../usage/network-setup.md). @@ -40,7 +40,7 @@ startup_manifest = '/etc/ankaios/state.yaml' # The address to which the server should listen, either a TCP socket # address including the port (`:`) or a Unix domain socket # address (`unix://`). -address = 'unix:///run/ankaios/server.sock' +address = 'unix:///run/ankaios.sock' # Optional group applied to the Unix domain socket file's group ownership. # This option is only valid with 'unix://' addresses. @@ -101,10 +101,9 @@ version = 'v1' # (a-z and A-Z), numbers and the symbols "-" and "_". name = 'agent_1' -# The server address, either a TCP address (`https://:`) or a +# The server URL, either a TCP address (`https://:`) or a # Unix domain socket address (`unix://`). -# The legacy field name 'server_url' is still accepted as an alias. -address = 'unix:///run/ankaios/server.sock' +server_url = 'unix:///run/ankaios.sock' # An existing path where to manage the fifo files. # If not set, defaults to '$TMPDIR/ankaios/' (falls back to '/tmp/ankaios/' if TMPDIR is not set). @@ -148,7 +147,7 @@ address = 'unix:///run/ankaios/server.sock' !!! note - If no `address` is configured and no configuration file is used, the Ankaios agent defaults to + If no `server_url` is configured and no configuration file is used, the Ankaios agent defaults to the TCP address `https://127.0.0.1:25551`. ### Ankaios CLI Configuration (`ank.conf`) @@ -173,13 +172,12 @@ version = 'v1' # no_wait = false [default] -# The address of the Ankaios server, either a TCP address +# The URL of the Ankaios server, either a TCP address # (`https://:`) or a Unix domain socket address # (`unix://`). # If started in insecure mode then the HTTP protocol shall be used, # otherwise the HTTPS protocol shall be used. -# The legacy field name 'server_url' is still accepted as an alias. -address = 'unix:///run/ankaios/server.sock' +server_url = 'unix:///run/ankaios.sock' # The flag to disable TLS communication with the server. # If set to 'true', then the CLI shall not use TLS. diff --git a/doc/docs/usage/network-setup.md b/doc/docs/usage/network-setup.md index f6a13a60a..df54a1852 100644 --- a/doc/docs/usage/network-setup.md +++ b/doc/docs/usage/network-setup.md @@ -1,7 +1,7 @@ # Connecting Ankaios components over the network The standard [installation](installation.md) configures the server, agent and CLI to communicate over a local Unix domain socket (see the respective [configuration files](../reference/config-files.md)), which relies on filesystem permissions and only works when all components run on the same host. - To connect an agent or the CLI from a different host, or simply to reach the server over the network, the `address` in the server, agent and CLI configuration files needs to be switched from a `unix://` address to a network address (`:`). + To connect an agent or the CLI from a different host, or simply to reach the server over the network, the `address` in the server configuration file and the `server_url` in the agent and CLI configuration files need to be switched from a `unix://` address to a network address (`:`). !!! info @@ -212,7 +212,7 @@ key_pem = '/etc/ankaios/certs/ank-server-key.pem' Similarly, switch `/etc/ankaios/ank-agent.conf` to the network address of the server, using the `https://` scheme, and set `insecure` to `false`: ```toml -address = 'https://127.0.0.1:25551' +server_url = 'https://127.0.0.1:25551' ``` For the agent add the following lines to `/etc/ankaios/ank-agent.conf`: @@ -256,7 +256,7 @@ ank --ca_pem=/etc/ankaios/certs/ca.pem --crt_pem="$HOME/.config/ankaios/ank.pem" ``` Or you can also configure the mTLS certificates in the CLI configuration file `~/.config/ankaios/ank.conf`. -In any case, make sure the `address` in there is set to the network address of the server (e.g. `https://127.0.0.1:25551`) and `insecure = false` is set to prevent a warning when mTLS certificates are provided. +In any case, make sure the `server_url` in there is set to the network address of the server (e.g. `https://127.0.0.1:25551`) and `insecure = false` is set to prevent a warning when mTLS certificates are provided. ## Use network communication without TLS @@ -271,10 +271,10 @@ address = '0.0.0.0:25551' insecure = true ``` -Replace the default `address` in `/etc/ankaios/ank-agent.conf` with the network address of the server and mark the connection as insecure: +Replace the default `server_url` in `/etc/ankaios/ank-agent.conf` with the network address of the server and mark the connection as insecure: ```toml -address = 'http://:25551' +server_url = 'http://:25551' insecure = true ``` diff --git a/tools/aur/README.md b/tools/aur/README.md index b99ce1b4e..8d3f9108c 100644 --- a/tools/aur/README.md +++ b/tools/aur/README.md @@ -19,7 +19,7 @@ They are intended to be run manually by the package maintainer outside the dev c | `PKGBUILD-ankaios-bin.m4` | Template for the prebuilt-binary AUR package | | `PKGBUILD-ankaios-git.m4` | Template for the git-head AUR package | | `ankaios-cli.install`, `ankaios.sysusers` | Asset files copied into each AUR package repository | -| `ankaios.sysusers` | `systemd-sysusers` fragment creating the system group `ankaios` used for the server's Unix domain socket (`/run/ankaios/server.sock`), installed to `/usr/lib/sysusers.d/ankaios.conf` | +| `ankaios.sysusers` | `systemd-sysusers` fragment creating the system group `ankaios` used for the server's Unix domain socket (`/run/ankaios.sock`), installed to `/usr/lib/sysusers.d/ankaios.conf` | ## Required environment From be82d14d7bad443feaaea29309011d17febc8329 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Mon, 12 Oct 2026 12:18:34 +0200 Subject: [PATCH 44/55] Add missing file for arch package --- tools/aur/README.md | 1 - tools/aur/ankaios.sysusers | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tools/aur/ankaios.sysusers diff --git a/tools/aur/README.md b/tools/aur/README.md index 8d3f9108c..fab777e79 100644 --- a/tools/aur/README.md +++ b/tools/aur/README.md @@ -19,7 +19,6 @@ They are intended to be run manually by the package maintainer outside the dev c | `PKGBUILD-ankaios-bin.m4` | Template for the prebuilt-binary AUR package | | `PKGBUILD-ankaios-git.m4` | Template for the git-head AUR package | | `ankaios-cli.install`, `ankaios.sysusers` | Asset files copied into each AUR package repository | -| `ankaios.sysusers` | `systemd-sysusers` fragment creating the system group `ankaios` used for the server's Unix domain socket (`/run/ankaios.sock`), installed to `/usr/lib/sysusers.d/ankaios.conf` | ## Required environment diff --git a/tools/aur/ankaios.sysusers b/tools/aur/ankaios.sysusers new file mode 100644 index 000000000..f29ea7717 --- /dev/null +++ b/tools/aur/ankaios.sysusers @@ -0,0 +1 @@ +g ankaios - From 7d706bec182cb0a30aec4e251b4b9761997a5be0 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:01:41 +0200 Subject: [PATCH 45/55] Remove creation of run folder from install script As the run folder is now created by systemd, this is now longer needed. --- tools/install.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tools/install.sh b/tools/install.sh index 782a81ca3..3be7df114 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -11,7 +11,6 @@ HOME_CONFIG_DEST="${HOME}/.config/ankaios/" FILE_STARTUP_STATE="${CONFIG_DEST}/state.yaml" INSTALL_TYPE="both" SERVICE_DEST=/etc/systemd/system -ANK_SERVER_RUNTIME_DIR="/run/ankaios" ANK_SYSTEM_GROUP="ankaios" ANK_SERVER_SERVICE="ank-server" @@ -199,13 +198,6 @@ else echo "$$SERVICE_DEST not found. Skipping installation of systemd unit files for Ankaios" fi -if [[ "$INSTALL_TYPE" == server || "$INSTALL_TYPE" == both ]]; then - echo "Preparing server runtime directory: '${ANK_SERVER_RUNTIME_DIR}'" - $RUNTIME_SUDO mkdir -p "${ANK_SERVER_RUNTIME_DIR}" - $RUNTIME_SUDO chown "root:${ANK_SYSTEM_GROUP}" "${ANK_SERVER_RUNTIME_DIR}" - $RUNTIME_SUDO chmod 0750 "${ANK_SERVER_RUNTIME_DIR}" -fi - # Write sample state startup config if [[ "$INSTALL_TYPE" == server || "$INSTALL_TYPE" == both ]]; then if ! [ -s "$FILE_STARTUP_STATE" ]; then From 149feba18612b9560122460b68597f5856d9d759 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:51:01 +0200 Subject: [PATCH 46/55] Describe how to add user to ankaios group in installation.md --- doc/docs/usage/installation.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/docs/usage/installation.md b/doc/docs/usage/installation.md index be5525c01..d9b6ff771 100644 --- a/doc/docs/usage/installation.md +++ b/doc/docs/usage/installation.md @@ -69,6 +69,12 @@ The installation process automatically detects the platform and downloads the ap The installation path for the binaries is `/usr/local/bin`. The installation also creates systemd unit files, an uninstall script and default configuration files for the Ankaios server, agent, and CLI. The configuration files are never overwritten if they already exist, but new files for comparison reasons for users are provided with a `.confnew` suffix. +When installing the server, the script also creates the system group `ankaios`, which owns the default Unix domain socket. Add your user to this group so that the `ank` CLI can connect without `sudo`: + +```shell +sudo usermod -aG ankaios "$USER" +``` + Supported platforms: `linux/amd64`, `linux/arm64` !!! note @@ -154,6 +160,12 @@ The `ankaios` meta-package installs all components. Individual packages can be i The packages are compatible with Ubuntu 22.04+, Debian 12+ and other distributions based on glibc 2.35 or later. +Installing `ank-server` creates the system group `ankaios`, which owns the default Unix domain socket. Add your user to this group so that the `ank` CLI can connect without `sudo`: + +```shell +sudo usermod -aG ankaios "$USER" +``` + #### Uninstall ```shell @@ -209,6 +221,12 @@ You can also install: | `ankaios-bin` | Pre-built binaries from releases | | `ankaios-git` | Build from the latest git revision | +Installing `ankaios-server` creates the system group `ankaios`, which owns the default Unix domain socket. Add your user to this group so that the `ank` CLI can connect without `sudo`: + +```shell +sudo usermod -aG ankaios "$USER" +``` + !!! note If `ankaios-server` and `ankaios-agent` are installed, you can enable and start the services with: From 3e09ac56c3f7dcd48f6e257d08025f33b855ae7d Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:58:08 +0200 Subject: [PATCH 47/55] Improve wording and introduce insecure example in ank.conf again Co-authored-by: Holger Dormann --- ank/config/ank.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ank/config/ank.conf b/ank/config/ank.conf index 2331ac900..446a1637c 100644 --- a/ank/config/ank.conf +++ b/ank/config/ank.conf @@ -24,7 +24,8 @@ server_url = 'unix:///run/ankaios.sock' # The flag to disable TLS communication with the server. # If set to 'true', then the CLI shall not use TLS. -# This option must not be used with unix:// addresses. +# This option is only available when using http communication. +# insecure = false # The path to the ca certificate pem file. # TLS certificate options are only valid with TCP addresses. From 02b01027cba552539a85f48ac477cc2f08736b5d Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:44:08 +0200 Subject: [PATCH 48/55] Remove commend about UDS no intended for production Co-authored-by: Holger Dormann --- doc/docs/usage/installation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/docs/usage/installation.md b/doc/docs/usage/installation.md index d9b6ff771..6aeddfe84 100644 --- a/doc/docs/usage/installation.md +++ b/doc/docs/usage/installation.md @@ -48,8 +48,7 @@ Ankaios uses the `nerdctl` command-line interface (CLI) to manage containers wit ## Installation methods By default, the server, agent and CLI communicate over a local Unix domain socket, which relies on filesystem permissions and only works when all components run on the same host. -This default is not intended for production use. -For communication over the network, follow the [network setup instructions](network-setup.md) after installing Ankaios. +For a multi-node setup communication over the network has to be used. Follow the [network setup instructions](network-setup.md) after installing Ankaios. ### Install script From 2774145321384e96f1bdd991116da58196a6fea2 Mon Sep 17 00:00:00 2001 From: Holger Dormann Date: Mon, 12 Oct 2026 14:39:53 +0000 Subject: [PATCH 49/55] Add post-installation steps. --- doc/docs/usage/installation.md | 35 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/doc/docs/usage/installation.md b/doc/docs/usage/installation.md index 6aeddfe84..fcc20a0cb 100644 --- a/doc/docs/usage/installation.md +++ b/doc/docs/usage/installation.md @@ -14,6 +14,8 @@ Ankaios works with most Linux distributions and has been tested with Ubuntu 22.0 Detailed installation steps, including instructions on setting up container runtimes, choosing an installation method, and installing specific Ankaios versions, are provided below. +See also the [post-installation steps](#post-installation-steps). + ## System requirements Ankaios currently requires a Linux OS and is available for x86_64 and arm64 @@ -68,12 +70,6 @@ The installation process automatically detects the platform and downloads the ap The installation path for the binaries is `/usr/local/bin`. The installation also creates systemd unit files, an uninstall script and default configuration files for the Ankaios server, agent, and CLI. The configuration files are never overwritten if they already exist, but new files for comparison reasons for users are provided with a `.confnew` suffix. -When installing the server, the script also creates the system group `ankaios`, which owns the default Unix domain socket. Add your user to this group so that the `ank` CLI can connect without `sudo`: - -```shell -sudo usermod -aG ankaios "$USER" -``` - Supported platforms: `linux/amd64`, `linux/arm64` !!! note @@ -96,7 +92,8 @@ To install a specific version run the following command and substitute `/install.sh | bash -s -- -v ``` -For available versions see the [list of releases](https://github.com/eclipse-ankaios/ankaios/tags). +For available versions see the [list of releases](https://github.com/eclipse-ankaios/ankaios/tags) +and also check the [post-installation steps](#post-installation-steps). #### Set the log level for `ank-server` and `ank-agent` services @@ -159,11 +156,7 @@ The `ankaios` meta-package installs all components. Individual packages can be i The packages are compatible with Ubuntu 22.04+, Debian 12+ and other distributions based on glibc 2.35 or later. -Installing `ank-server` creates the system group `ankaios`, which owns the default Unix domain socket. Add your user to this group so that the `ank` CLI can connect without `sudo`: - -```shell -sudo usermod -aG ankaios "$USER" -``` +See also the [post-installation steps](#post-installation-steps). #### Uninstall @@ -220,18 +213,14 @@ You can also install: | `ankaios-bin` | Pre-built binaries from releases | | `ankaios-git` | Build from the latest git revision | -Installing `ankaios-server` creates the system group `ankaios`, which owns the default Unix domain socket. Add your user to this group so that the `ank` CLI can connect without `sudo`: - -```shell -sudo usermod -aG ankaios "$USER" -``` - !!! note If `ankaios-server` and `ankaios-agent` are installed, you can enable and start the services with: `sudo systemctl enable --now ank-server ank-agent` +See also the [post-installation steps](#post-installation-steps). + #### Uninstall ```shell @@ -252,3 +241,13 @@ This is useful if the automatic detection of the platform is failing in case of ### Build from source For building Ankaios from source see [Build](../development/build.md). + +## Post-installation steps + +Installing `ankaios-server` creates the system group `ankaios`, which owns the default Unix domain socket. Add your user to this group so that the `ank` CLI can connect without `sudo`: + +```shell +sudo usermod -aG ankaios "$USER" +``` + +Log out and log back in so that your group membership is re-evaluated. From 74493b0c19c30df32dccb3fb08a03724b86abc21 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Tue, 13 Oct 2026 09:58:21 +0200 Subject: [PATCH 50/55] Switch from serde_yaml to yaml_serde As serde_yaml is no longer maintained, switching to maintained fork. --- Cargo.lock | 52 ++++----- agent/Cargo.toml | 2 +- .../containerd/containerd_runtime_config.rs | 4 +- .../podman/podman_runtime_config.rs | 4 +- .../podman_kube_control_interface.rs | 60 +++++----- .../podman_kube/podman_kube_runtime.rs | 2 +- .../podman_kube/podman_kube_runtime_config.rs | 4 +- ank/Cargo.toml | 2 +- ank/src/cli_commands/apply_manifests.rs | 20 ++-- ank/src/cli_commands/get_events.rs | 2 +- ank/src/cli_commands/get_state.rs | 10 +- ank/src/cli_commands/set_state.rs | 14 +-- ank/src/cli_error.rs | 4 +- ank_schema/Cargo.toml | 2 +- ankaios_api/Cargo.toml | 2 +- ankaios_api/build/proto_annotations.rs | 6 +- ankaios_api/src/ank_base/complete_state.rs | 4 +- ankaios_api/src/ank_base/workload.rs | 4 +- ankaios_api/src/helpers.rs | 10 +- ankaios_api/src/test_utils/spec_factories.rs | 2 +- common/Cargo.toml | 2 +- common/src/state_manipulation/object.rs | 42 +++---- examples/rust_sdk_hello/Cargo.toml | 2 +- examples/rust_sdk_logging/Cargo.toml | 2 +- server/Cargo.toml | 2 +- server/src/ankaios_server/event_handler.rs | 50 ++++----- server/src/ankaios_server/server_state.rs | 2 +- server/src/ankaios_server/state_comparator.rs | 106 +++++++++--------- server/src/main.rs | 6 +- .../control_interface_tester/Cargo.toml | 2 +- .../control_interface_tester/src/main.rs | 4 +- 31 files changed, 215 insertions(+), 215 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be13d8157..e8e41d295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,12 +56,12 @@ dependencies = [ "mockall_double", "serde", "serde_json", - "serde_yaml", "tabled", "tempfile", "tokio", "toml", "uuid", + "yaml_serde", ] [[package]] @@ -88,13 +88,13 @@ dependencies = [ "semver", "serde", "serde_json", - "serde_yaml", "sha256", "sysinfo", "tempfile", "tokio", "toml", "umask", + "yaml_serde", ] [[package]] @@ -106,7 +106,7 @@ dependencies = [ "jsonschema", "schemars", "serde_json", - "serde_yaml", + "yaml_serde", ] [[package]] @@ -125,10 +125,10 @@ dependencies = [ "prost", "regex", "serde", - "serde_yaml", "tempfile", "tokio", "toml", + "yaml_serde", ] [[package]] @@ -141,11 +141,11 @@ dependencies = [ "schemars", "serde", "serde_json", - "serde_yaml", "sha256", "spec_macros", "tonic", "tonic-prost-build", + "yaml_serde", ] [[package]] @@ -489,10 +489,10 @@ dependencies = [ "prost", "semver", "serde", - "serde_yaml", "tempfile", "tokio", "toml", + "yaml_serde", ] [[package]] @@ -504,7 +504,7 @@ dependencies = [ "prost", "serde", "serde_json", - "serde_yaml", + "yaml_serde", ] [[package]] @@ -1492,6 +1492,12 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "libyaml-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e126dda6f34391ab7b444f9922055facc83c07a910da3eb16f1e4d9c45dc777" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2393,19 +2399,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sha2" version = "0.10.9" @@ -3002,12 +2995,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "untrusted" version = "0.9.0" @@ -3384,6 +3371,19 @@ dependencies = [ "time", ] +[[package]] +name = "yaml_serde" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b729a08a9a6be689bbad3e2bf8015926db54b6622cc89c3a5f7dc174b9e918" +dependencies = [ + "indexmap", + "itoa", + "libyaml-rs", + "ryu", + "serde", +] + [[package]] name = "yoke" version = "0.8.3" diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 8490f9f63..d456b59ae 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1.41", features = [ nix = { version = "0.31", features = ["fs", "user"] } async-trait = "0.1" serde = { version = "1.0", features = ["derive"] } -serde_yaml = "0.9" +yaml_serde = "0.10" clap = { version = "4.5", features = ["derive", "env"] } futures-util = "0.3" serde_json = "1.0" diff --git a/agent/src/runtime_connectors/containerd/containerd_runtime_config.rs b/agent/src/runtime_connectors/containerd/containerd_runtime_config.rs index a073d491d..c7e42d015 100644 --- a/agent/src/runtime_connectors/containerd/containerd_runtime_config.rs +++ b/agent/src/runtime_connectors/containerd/containerd_runtime_config.rs @@ -49,7 +49,7 @@ impl TryFrom<&WorkloadSpec> for ContainerdRuntimeConfig { workload_spec.runtime )); } - match serde_yaml::from_str(workload_spec.runtime_config.as_str()) { + match yaml_serde::from_str(workload_spec.runtime_config.as_str()) { Ok(workload_cfg) => Ok(workload_cfg), Err(e) => Err(e.to_string()), } @@ -69,7 +69,7 @@ mod tests { use super::{ContainerdRuntimeConfig, NerdctlRunConfig}; use crate::runtime_connectors::containerd::containerd_runtime::CONTAINERD_RUNTIME_NAME; - use ankaios_api::test_utils::{generate_test_workload_with_params, fixtures}; + use ankaios_api::test_utils::{fixtures, generate_test_workload_with_params}; const DIFFERENT_RUNTIME_NAME: &str = "different-runtime-name"; diff --git a/agent/src/runtime_connectors/podman/podman_runtime_config.rs b/agent/src/runtime_connectors/podman/podman_runtime_config.rs index 4de49ec38..0948b66b1 100644 --- a/agent/src/runtime_connectors/podman/podman_runtime_config.rs +++ b/agent/src/runtime_connectors/podman/podman_runtime_config.rs @@ -49,7 +49,7 @@ impl TryFrom<&WorkloadSpec> for PodmanRuntimeConfig { workload_spec.runtime )); } - match serde_yaml::from_str(workload_spec.runtime_config.as_str()) { + match yaml_serde::from_str(workload_spec.runtime_config.as_str()) { Ok(workload_cfg) => Ok(workload_cfg), Err(e) => Err(e.to_string()), } @@ -71,7 +71,7 @@ mod tests { podman::podman_runtime::PODMAN_RUNTIME_NAME, podman_cli::PodmanRunConfig, }; - use ankaios_api::test_utils::{generate_test_workload_with_params, fixtures}; + use ankaios_api::test_utils::{fixtures, generate_test_workload_with_params}; const DIFFERENT_RUNTIME_NAME: &str = "different-runtime-name"; diff --git a/agent/src/runtime_connectors/podman_kube/podman_kube_control_interface.rs b/agent/src/runtime_connectors/podman_kube/podman_kube_control_interface.rs index 5d61b0b0f..d78f5c012 100644 --- a/agent/src/runtime_connectors/podman_kube/podman_kube_control_interface.rs +++ b/agent/src/runtime_connectors/podman_kube/podman_kube_control_interface.rs @@ -16,7 +16,7 @@ use super::podman_kube_runtime_config::PodmanKubeRuntimeConfig; use crate::runtime_connectors::{RuntimeError, podman_cli::API_PIPES_MOUNT_POINT}; use serde::Deserialize; -use serde_yaml::{Deserializer, Mapping, Value}; +use yaml_serde::{Deserializer, Mapping, Value}; use std::path::Path; const TARGET_PATH_LENGTH: usize = 2; @@ -279,7 +279,7 @@ fn create_control_interface_volume(control_interface_path: &Path) -> Value { } fn serialize_yaml_manifest(manifest: &Value) -> Result { - serde_yaml::to_string(manifest) + yaml_serde::to_string(manifest) .map_err(|e| RuntimeError::Unsupported(format!("Failed to serialize manifest: {e}"))) } @@ -306,7 +306,7 @@ mod tests { generate_test_workload_with_runtime_config, }; - use serde_yaml::Value; + use yaml_serde::Value; #[test] fn utest_control_interface_target_valid() { @@ -483,7 +483,7 @@ metadata: #[test] fn utest_get_metadata_name_success() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -499,7 +499,7 @@ metadata: #[test] fn utest_get_metadata_name_missing_metadata() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -514,7 +514,7 @@ kind: Pod #[test] fn utest_get_metadata_name_missing_name() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -531,7 +531,7 @@ metadata: #[test] fn utest_get_metadata_name_name_not_string() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -550,7 +550,7 @@ metadata: // [utest->swdd~podman-kube-injects-control-interface-volume~1] #[test] fn utest_should_inject_control_interface_not_pod_kind() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Service @@ -568,7 +568,7 @@ metadata: // [utest->swdd~podman-kube-mounts-control-interface~2] #[test] fn utest_should_inject_control_interface_wrong_pod_name() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -586,7 +586,7 @@ metadata: // [utest->swdd~podman-kube-mounts-control-interface~2] #[test] fn utest_should_inject_control_interface_matching_pod() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -603,7 +603,7 @@ metadata: #[test] fn utest_should_inject_control_interface_matching_deployment() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Deployment @@ -620,7 +620,7 @@ metadata: #[test] fn utest_should_inject_control_interface_wrong_deployment_name() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Deployment @@ -637,7 +637,7 @@ metadata: #[test] fn utest_should_inject_control_interface_missing_kind() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 metadata: @@ -653,7 +653,7 @@ metadata: #[test] fn utest_find_containers_spec_pod_returns_spec() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -676,7 +676,7 @@ spec: #[test] fn utest_find_containers_spec_deployment_returns_template_spec() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: apps/v1 kind: Deployment @@ -702,7 +702,7 @@ spec: #[test] fn utest_find_containers_spec_none_when_missing_spec() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -717,7 +717,7 @@ metadata: #[test] fn utest_find_containers_spec_none_when_no_containers_and_no_template_spec() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -789,7 +789,7 @@ spec: .contains("control-interface-volume") ); - let parsed: Value = serde_yaml::from_str(&workload_config.manifest).unwrap(); + let parsed: Value = yaml_serde::from_str(&workload_config.manifest).unwrap(); let vol_mounts = &parsed["spec"]["containers"][0]["volumeMounts"]; assert!(!vol_mounts.as_sequence().unwrap().is_empty()); @@ -800,7 +800,7 @@ spec: // [utest->swdd~podman-kube-injects-control-interface-volume-mount~1] #[test] fn utest_inject_volume_mount_success() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -832,7 +832,7 @@ spec: // [utest->swdd~podman-kube-injects-control-interface-volume-mount~1] #[test] fn utest_inject_volume_mount_missing_containers() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -850,7 +850,7 @@ spec: // [utest->swdd~podman-kube-injects-control-interface-volume-mount~1] #[test] fn utest_inject_volume_mount_container_missing_name() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -870,7 +870,7 @@ spec: // [utest->swdd~podman-kube-injects-control-interface-volume-mount~1] #[test] fn utest_inject_volume_mount_target_container_not_found() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -894,7 +894,7 @@ spec: // [utest->swdd~podman-kube-injects-control-interface-volume-mount~1] #[test] fn utest_add_control_interface_mount_with_existing_mounts() { - let mut container = serde_yaml::from_str::( + let mut container = yaml_serde::from_str::( r#" name: test-container volumeMounts: @@ -915,7 +915,7 @@ volumeMounts: // [utest->swdd~podman-kube-injects-control-interface-volume-mount~1] #[test] fn utest_add_control_interface_mount_no_volume_mounts() { - let mut container = serde_yaml::from_str::( + let mut container = yaml_serde::from_str::( r#" name: test-container image: test-image @@ -934,7 +934,7 @@ image: test-image // [utest->swdd~podman-kube-injects-control-interface-volume~1] #[test] fn utest_inject_control_volume_success_with_existing_volume_wl() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -961,7 +961,7 @@ spec: // [utest->swdd~podman-kube-injects-control-interface-volume-mount~1] #[test] fn utest_inject_control_volume_success_with_missing_volume_wl() { - let mut manifest = serde_yaml::from_str::( + let mut manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -1001,7 +1001,7 @@ spec: #[test] fn utest_serialize_yaml_manifest_success() { - let manifest = serde_yaml::from_str::( + let manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -1023,7 +1023,7 @@ metadata: // [utest->swdd~podman-kube-injects-control-interface-volume~1] #[test] fn utest_process_manifest_list_mixed_manifests() { - let pod_manifest = serde_yaml::from_str::( + let pod_manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod @@ -1039,7 +1039,7 @@ spec: ) .unwrap(); - let service_manifest = serde_yaml::from_str::( + let service_manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Service @@ -1099,7 +1099,7 @@ spec: #[test] fn utest_add_control_interface_in_correct_manifest_fails_when_target_pod_not_found() { - let pod_manifest = serde_yaml::from_str::( + let pod_manifest = yaml_serde::from_str::( r#" apiVersion: v1 kind: Pod diff --git a/agent/src/runtime_connectors/podman_kube/podman_kube_runtime.rs b/agent/src/runtime_connectors/podman_kube/podman_kube_runtime.rs index ab4e390be..657f2d229 100644 --- a/agent/src/runtime_connectors/podman_kube/podman_kube_runtime.rs +++ b/agent/src/runtime_connectors/podman_kube/podman_kube_runtime.rs @@ -99,7 +99,7 @@ impl PodmanKubeWorkloadInfoStore { .await .map_err(|err| format!("Could not read config from volume: {err:?}")) .and_then(|json| { - serde_yaml::from_str::(&json) + yaml_serde::from_str::(&json) .map_err(|err| format!("Could not parse config read from volume: {err:?}")) }) .map_err(RuntimeError::Create)?; diff --git a/agent/src/runtime_connectors/podman_kube/podman_kube_runtime_config.rs b/agent/src/runtime_connectors/podman_kube/podman_kube_runtime_config.rs index 2d489db32..693f4fdce 100644 --- a/agent/src/runtime_connectors/podman_kube/podman_kube_runtime_config.rs +++ b/agent/src/runtime_connectors/podman_kube/podman_kube_runtime_config.rs @@ -47,7 +47,7 @@ impl TryFrom<&WorkloadSpec> for PodmanKubeRuntimeConfig { )); } - match serde_yaml::from_str(workload.runtime_config.as_str()) { + match yaml_serde::from_str(workload.runtime_config.as_str()) { Ok(workload_cfg) => Ok(workload_cfg), Err(e) => Err(e.to_string()), } @@ -66,7 +66,7 @@ impl TryFrom<&WorkloadSpec> for PodmanKubeRuntimeConfig { mod tests { use super::{PODMAN_KUBE_RUNTIME_NAME, PodmanKubeRuntimeConfig}; - use ankaios_api::test_utils::{generate_test_workload_with_params, fixtures}; + use ankaios_api::test_utils::{fixtures, generate_test_workload_with_params}; const DIFFERENT_RUNTIME_NAME: &str = "different-runtime-name"; const MANIFEST_CONTENT: &str = "kube, man"; diff --git a/ank/Cargo.toml b/ank/Cargo.toml index 6423bf795..f59dc0928 100644 --- a/ank/Cargo.toml +++ b/ank/Cargo.toml @@ -51,7 +51,7 @@ tokio = { version = "1.41", features = [ ] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_yaml = "0.9" +yaml_serde = "0.10" tabled = "0.17" tempfile = "3.17" toml = "0.9" diff --git a/ank/src/cli_commands/apply_manifests.rs b/ank/src/cli_commands/apply_manifests.rs index 456acf334..8462e4d36 100644 --- a/ank/src/cli_commands/apply_manifests.rs +++ b/ank/src/cli_commands/apply_manifests.rs @@ -67,7 +67,7 @@ fn detect_api_version(obj: &Object, obj_paths: &[Path]) -> Resultswdd~cli-apply-manifest-check-for-api-version-compatibility~1] // [impl->swdd~cli-apply-manifest-accepts-v01-api-version~1] pub fn parse_manifest(manifest: &mut InputSourcePair) -> Result<(Object, Vec), String> { - let state_obj_parsing_check: serde_yaml::Value = serde_yaml::from_reader(&mut manifest.1) + let state_obj_parsing_check: yaml_serde::Value = yaml_serde::from_reader(&mut manifest.1) .map_err(|err| format!("Invalid manifest data provided: {err}"))?; // [impl->swdd~cli-validates-manifest-against-schema~1] @@ -114,7 +114,7 @@ pub fn handle_agent_overwrite( state_obj .set( &workload_agent_mask, - serde_yaml::Value::String(agent_name.to_owned()), + yaml_serde::Value::String(agent_name.to_owned()), ) .map_err(|_| "Could not find workload to update.".to_owned())?; } else if state_obj.get(&workload_agent_mask).is_none() { @@ -272,7 +272,7 @@ mod tests { }; use mockall::predicate::eq; - use serde_yaml::Value; + use yaml_serde::Value; use std::io::{Cursor, Read}; mockall::lazy_static! { @@ -468,7 +468,7 @@ mod tests { #[test] fn utest_update_request_obj_ok() { let mut req_obj = Object::default(); - let content_value: Value = serde_yaml::from_str( + let content_value: Value = yaml_serde::from_str( r#" workloads: simple: @@ -491,7 +491,7 @@ mod tests { #[test] fn utest_update_request_obj_failed_same_workload_names() { - let content_value: Value = serde_yaml::from_str( + let content_value: Value = yaml_serde::from_str( r#" workloads: same_workload_name: {} @@ -511,7 +511,7 @@ mod tests { #[test] fn utest_update_request_obj_delete_mode_on_ok() { let mut req_obj = Object::default(); - let content_value: Value = serde_yaml::from_str( + let content_value: Value = yaml_serde::from_str( r#" workloads: simple: @@ -711,7 +711,7 @@ mod tests { let _ = manifest_content.clone().read_to_string(&mut data); let expected_complete_state_obj = CompleteState { - desired_state: serde_yaml::from_str(&data).unwrap(), + desired_state: yaml_serde::from_str(&data).unwrap(), ..Default::default() }; @@ -903,7 +903,7 @@ mod tests { let _ = manifest_content.clone().read_to_string(&mut manifest_data); let updated_state = CompleteState { - desired_state: serde_yaml::from_str(&manifest_data).unwrap(), + desired_state: yaml_serde::from_str(&manifest_data).unwrap(), ..Default::default() }; @@ -1015,7 +1015,7 @@ mod tests { let _ = manifest_content.clone().read_to_string(&mut manifest_data); let updated_state = CompleteState { - desired_state: serde_yaml::from_str(&manifest_data).unwrap(), + desired_state: yaml_serde::from_str(&manifest_data).unwrap(), ..Default::default() }; println!("Expected new state: {updated_state:?}"); @@ -1130,7 +1130,7 @@ mod tests { let _ = manifest_content.clone().read_to_string(&mut manifest_data); let updated_state = CompleteState { - desired_state: serde_yaml::from_str(&manifest_data).unwrap(), + desired_state: yaml_serde::from_str(&manifest_data).unwrap(), ..Default::default() }; diff --git a/ank/src/cli_commands/get_events.rs b/ank/src/cli_commands/get_events.rs index a402e4ca9..8b8053d19 100644 --- a/ank/src/cli_commands/get_events.rs +++ b/ank/src/cli_commands/get_events.rs @@ -98,7 +98,7 @@ mod event_output { ) -> Result<(), super::CliError> { match output_format { OutputFormat::Yaml => { - let yaml_output = serde_yaml::to_string(&event) + let yaml_output = yaml_serde::to_string(&event) .map_err(|err| CliError::ExecutionError(err.to_string()))?; output!("---\n{}", yaml_output.trim_end()); } diff --git a/ank/src/cli_commands/get_state.rs b/ank/src/cli_commands/get_state.rs index 6a1429e30..a69d550b4 100644 --- a/ank/src/cli_commands/get_state.rs +++ b/ank/src/cli_commands/get_state.rs @@ -42,7 +42,7 @@ impl CliCommands { match output_format { // [impl -> swdd~cli-shall-support-desired-state-yaml~1] - OutputFormat::Yaml => Ok(serde_yaml::to_string(&filtered_complete_state)?), + OutputFormat::Yaml => Ok(yaml_serde::to_string(&filtered_complete_state)?), // [impl -> swdd~cli-shall-support-desired-state-json~1] OutputFormat::Json => Ok(serde_json::to_string_pretty(&filtered_complete_state)?), } @@ -120,7 +120,7 @@ mod tests { }; let cmd_text = cmd.get_state(vec![], OutputFormat::Yaml).await.unwrap(); - let expected_text = serde_yaml::to_string(&test_data).unwrap(); + let expected_text = yaml_serde::to_string(&test_data).unwrap(); assert_eq!(cmd_text, expected_text); } @@ -182,7 +182,7 @@ mod tests { .await .unwrap(); - let expected_single_field_result_text = serde_yaml::to_string(&test_data).unwrap(); + let expected_single_field_result_text = yaml_serde::to_string(&test_data).unwrap(); assert_eq!(cmd_text, expected_single_field_result_text); } @@ -235,7 +235,7 @@ mod tests { ) .await .unwrap(); - let expected_text = serde_yaml::to_string(&test_data).unwrap(); + let expected_text = yaml_serde::to_string(&test_data).unwrap(); assert_eq!(cmd_text, expected_text); } @@ -262,7 +262,7 @@ mod tests { .await .unwrap(); - let expected_text = serde_yaml::to_string(&test_data).unwrap(); + let expected_text = yaml_serde::to_string(&test_data).unwrap(); assert_eq!(cmd_text, expected_text); } } diff --git a/ank/src/cli_commands/set_state.rs b/ank/src/cli_commands/set_state.rs index 054bc632e..79db65886 100644 --- a/ank/src/cli_commands/set_state.rs +++ b/ank/src/cli_commands/set_state.rs @@ -35,7 +35,7 @@ async fn process_inputs(reader: R, state_object_file: &str) -> Result(reader: R, state_object_file: &str) -> Result io::Result { @@ -135,7 +135,7 @@ mod tests { let temp_obj = process_inputs(reader, &state_object_file).await.unwrap(); - let value: Value = serde_yaml::from_str(SAMPLE_CONFIG).unwrap(); + let value: Value = yaml_serde::from_str(SAMPLE_CONFIG).unwrap(); let expected_obj = value.into(); assert_eq!(temp_obj, expected_obj); @@ -150,7 +150,7 @@ mod tests { .await .unwrap(); - let value: Value = serde_yaml::from_str(SAMPLE_CONFIG).unwrap(); + let value: Value = yaml_serde::from_str(SAMPLE_CONFIG).unwrap(); let expected_obj = value.into(); assert_eq!(temp_obj, expected_obj); @@ -187,7 +187,7 @@ mod tests { }; let update_mask = vec!["desiredState.workloads.nginx.restartPolicy".to_string()]; - let state_object_file = serde_yaml::to_string(&updated_state).unwrap(); + let state_object_file = yaml_serde::to_string(&updated_state).unwrap(); let mut mock_server_connection = MockServerConnection::default(); mock_server_connection diff --git a/ank/src/cli_error.rs b/ank/src/cli_error.rs index d5aa3e47c..62a60eaef 100644 --- a/ank/src/cli_error.rs +++ b/ank/src/cli_error.rs @@ -38,8 +38,8 @@ impl fmt::Display for CliError { } } -impl From for CliError { - fn from(value: serde_yaml::Error) -> Self { +impl From for CliError { + fn from(value: yaml_serde::Error) -> Self { CliError::YamlSerialization(format!("{value}")) } } diff --git a/ank_schema/Cargo.toml b/ank_schema/Cargo.toml index 2bc2d916b..e93fb496b 100644 --- a/ank_schema/Cargo.toml +++ b/ank_schema/Cargo.toml @@ -13,7 +13,7 @@ ankaios-api = { path = "../ankaios_api" } serde_json = "1.0" schemars = "1.0" jsonschema = { version = "0.44", default-features = false } -serde_yaml = "0.9" +yaml_serde = "0.10" [build-dependencies] ankaios-api = { path = "../ankaios_api" } diff --git a/ankaios_api/Cargo.toml b/ankaios_api/Cargo.toml index 5a8e85f29..a07efdcea 100644 --- a/ankaios_api/Cargo.toml +++ b/ankaios_api/Cargo.toml @@ -13,7 +13,7 @@ spec_macros = { path = "../spec_macros" } prost = "0.14" tonic = "0.14" serde = { version = "1.0", features = ["derive"] } -serde_yaml = "0.9" +yaml_serde = "0.10" serde_json = "1.0" sha256 = "1.5" regex = "1.12" diff --git a/ankaios_api/build/proto_annotations.rs b/ankaios_api/build/proto_annotations.rs index 79acf1506..75c60c3b0 100644 --- a/ankaios_api/build/proto_annotations.rs +++ b/ankaios_api/build/proto_annotations.rs @@ -169,18 +169,18 @@ fn annotate_workload(builder: Builder) -> Builder { .field_attribute( "Files.files", // Yes, this is not a map, but this is the only way to get the desired serialization behavior without ! in the YAML and a custom serializer - "#[serde(with = \"serde_yaml::with::singleton_map_recursive\")]", + "#[serde(with = \"yaml_serde::with::singleton_map_recursive\")]", ) .field_attribute("File.FileContent", "#[serde(flatten)]") .enum_attribute("File.FileContent", "#[serde(rename_all = \"camelCase\")]") // Control Interface Access .field_attribute( "ControlInterfaceAccess.allowRules", - "#[serde(default, with = \"serde_yaml::with::singleton_map_recursive\", skip_serializing_if = \"Vec::is_empty\")]", + "#[serde(default, with = \"yaml_serde::with::singleton_map_recursive\", skip_serializing_if = \"Vec::is_empty\")]", ) .field_attribute( "ControlInterfaceAccess.denyRules", - "#[serde(default, with = \"serde_yaml::with::singleton_map_recursive\", skip_serializing_if = \"Vec::is_empty\")]", + "#[serde(default, with = \"yaml_serde::with::singleton_map_recursive\", skip_serializing_if = \"Vec::is_empty\")]", ) // The alias to filterMask is added to support the deprecated apiVersions: v0.1 .field_attribute("StateRule.filterMasks", "#[serde(alias = \"filterMask\")]") diff --git a/ankaios_api/src/ank_base/complete_state.rs b/ankaios_api/src/ank_base/complete_state.rs index 76dd86c33..6fb072fbf 100644 --- a/ankaios_api/src/ank_base/complete_state.rs +++ b/ankaios_api/src/ank_base/complete_state.rs @@ -140,7 +140,7 @@ mod tests { let ankaios_state = generate_test_state(); // serialize to sorted output - let sorted_state_string = serde_yaml::to_string(&ankaios_state).unwrap(); + let sorted_state_string = yaml_serde::to_string(&ankaios_state).unwrap(); let index_workload1 = sorted_state_string .find(fixtures::WORKLOAD_NAMES[0]) @@ -204,7 +204,7 @@ mod tests { assert_eq!(state_ankaios_no_version.api_version, "".to_string()); let file_without_api_version = ""; - let deserialization_result = serde_yaml::from_str::(file_without_api_version) + let deserialization_result = yaml_serde::from_str::(file_without_api_version) .unwrap_err() .to_string(); assert_eq!(deserialization_result, "missing field `apiVersion`"); diff --git a/ankaios_api/src/ank_base/workload.rs b/ankaios_api/src/ank_base/workload.rs index 19c933cbf..d689b437b 100644 --- a/ankaios_api/src/ank_base/workload.rs +++ b/ankaios_api/src/ank_base/workload.rs @@ -21,7 +21,7 @@ use crate::{CONSTRAINT_FIELD_DESCRIPTION, CURRENT_API_VERSION, PREVIOUS_API_VERS use serde::{Deserialize, Serialize}; -use serde_yaml::Value; +use yaml_serde::Value; use std::collections::HashMap; #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Default)] @@ -279,7 +279,7 @@ mod tests { DeleteCondition::DelCondNotPendingNorRunning, ); - let serialized_deleted_workload = serde_yaml::to_string(&deleted_workload).unwrap(); + let serialized_deleted_workload = yaml_serde::to_string(&deleted_workload).unwrap(); let indices = [ serialized_deleted_workload .find(fixtures::WORKLOAD_NAMES[0]) diff --git a/ankaios_api/src/helpers.rs b/ankaios_api/src/helpers.rs index ff49afadc..f1e5158d3 100644 --- a/ankaios_api/src/helpers.rs +++ b/ankaios_api/src/helpers.rs @@ -194,13 +194,13 @@ mod tests { key: String, } - let deserialized: TestStruct = serde_yaml::from_str(yaml).unwrap(); + let deserialized: TestStruct = yaml_serde::from_str(yaml).unwrap(); assert_eq!(deserialized.key, "some value"); } #[test] fn utest_serialize_to_ordered_map() { - let mut serializer = serde_yaml::Serializer::new(Vec::new()); + let mut serializer = yaml_serde::Serializer::new(Vec::new()); let mut map = HashMap::new(); map.insert("b".to_string(), 2); @@ -219,9 +219,9 @@ mod tests { key2: value2 "#; - let deserialized_map: HashMap = serde_yaml::from_str(yaml_map).unwrap(); + let deserialized_map: HashMap = yaml_serde::from_str(yaml_map).unwrap(); let adapted_map = - tag_adapter_deserializer(serde_yaml::Deserializer::from_str(yaml_map)).unwrap(); + tag_adapter_deserializer(yaml_serde::Deserializer::from_str(yaml_map)).unwrap(); assert_eq!(deserialized_map, adapted_map); let yaml_vec = r#" @@ -232,7 +232,7 @@ mod tests { "#; let adapted_map_vec = - tag_adapter_deserializer(serde_yaml::Deserializer::from_str(yaml_vec)).unwrap(); + tag_adapter_deserializer(yaml_serde::Deserializer::from_str(yaml_vec)).unwrap(); assert_eq!(deserialized_map, adapted_map_vec); } diff --git a/ankaios_api/src/test_utils/spec_factories.rs b/ankaios_api/src/test_utils/spec_factories.rs index 00c5655dc..b43b1daff 100644 --- a/ankaios_api/src/test_utils/spec_factories.rs +++ b/ankaios_api/src/test_utils/spec_factories.rs @@ -211,7 +211,7 @@ pub fn generate_test_agent_tags() -> TagsSpec { // ## ConfigMapSpec fixtures ## pub fn generate_test_config_map() -> ConfigMapSpec { - serde_yaml::from_str( + yaml_serde::from_str( format!( " config_1: diff --git a/common/Cargo.toml b/common/Cargo.toml index b88abb445..1b4d90c63 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -19,7 +19,7 @@ tokio = { version = "1.41", features = [ "process", ] } serde = { version = "1.0", features = ["derive"] } -serde_yaml = "0.9" +yaml_serde = "0.10" log = "0.4" prost = "0.14" semver = "1.0" diff --git a/common/src/state_manipulation/object.rs b/common/src/state_manipulation/object.rs index 16aeef717..69ea32672 100644 --- a/common/src/state_manipulation/object.rs +++ b/common/src/state_manipulation/object.rs @@ -19,7 +19,7 @@ use ankaios_api::ank_base::{ CompleteState, CompleteStateSpec, State, StateSpec, WILDCARD_SYMBOL, validate_field_pattern, }; -use serde_yaml::{ +use yaml_serde::{ Mapping, Value, from_value, mapping::Entry::{Occupied, Vacant}, to_value, @@ -39,8 +39,8 @@ impl Default for Object { } } -impl From for Object { - fn from(data: serde_yaml::Value) -> Self { +impl From for Object { + fn from(data: yaml_serde::Value) -> Self { Object { data } } } @@ -56,7 +56,7 @@ impl TryFrom<&toml::Value> for Object { } impl TryFrom<&StateSpec> for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_from(value: &StateSpec) -> Result { Ok(Object { @@ -66,7 +66,7 @@ impl TryFrom<&StateSpec> for Object { } impl TryFrom for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_from(value: StateSpec) -> Result { (&value).try_into() @@ -74,7 +74,7 @@ impl TryFrom for Object { } impl TryFrom for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_from(value: CompleteStateSpec) -> Result { Ok(Object { @@ -84,7 +84,7 @@ impl TryFrom for Object { } impl TryFrom for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_from(value: CompleteState) -> Result { Ok(Object { @@ -94,7 +94,7 @@ impl TryFrom for Object { } impl TryFrom<&CompleteStateSpec> for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_from(value: &CompleteStateSpec) -> Result { Ok(Object { @@ -104,7 +104,7 @@ impl TryFrom<&CompleteStateSpec> for Object { } impl TryFrom for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_from(value: State) -> Result { Ok(Object { @@ -114,7 +114,7 @@ impl TryFrom for Object { } impl TryInto for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_into(self) -> Result { from_value(self.data) @@ -122,7 +122,7 @@ impl TryInto for Object { } impl TryInto for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_into(self) -> Result { from_value(self.data) @@ -130,7 +130,7 @@ impl TryInto for Object { } impl TryInto for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_into(self) -> Result { from_value(self.data) @@ -138,14 +138,14 @@ impl TryInto for Object { } impl TryInto for Object { - type Error = serde_yaml::Error; + type Error = yaml_serde::Error; fn try_into(self) -> Result { from_value(self.data) } } -impl TryFrom for serde_yaml::Mapping { +impl TryFrom for yaml_serde::Mapping { type Error = String; fn try_from(value: Object) -> Result { @@ -172,7 +172,7 @@ fn generate_paths_from_yaml_node( key_str.to_owned() } Value::Number(key_number) if key_number.is_i64() || key_number.is_u64() => { - serde_yaml::to_string(key_number) + yaml_serde::to_string(key_number) .unwrap() .strip_suffix('\n') .unwrap() @@ -272,7 +272,7 @@ impl Object { } //[impl->swdd~common-state-manipulation-remove~1] - pub fn remove(&mut self, path: &Path) -> Result, String> { + pub fn remove(&mut self, path: &Path) -> Result, String> { let (path_head, path_last) = path.split_last()?; Ok(self @@ -407,7 +407,7 @@ mod tests { generate_test_workload_named, generate_test_workload_states_map_with_data, }; - use serde_yaml::{Mapping, Value}; + use yaml_serde::{Mapping, Value}; use std::collections::HashSet; #[test] @@ -753,7 +753,7 @@ mod tests { data.get(&format!("workloads.{}.restartPolicy", fixtures::WORKLOAD_NAMES[0]).into()); assert!(res.is_some()); - assert_eq!(res.expect(""), &serde_yaml::Value::from("ALWAYS")); + assert_eq!(res.expect(""), &yaml_serde::Value::from("ALWAYS")); } //[utest->swdd~common-state-manipulation-get~1] @@ -978,7 +978,7 @@ mod tests { } mod object { - use serde_yaml::Value; + use yaml_serde::Value; use ankaios_api::CURRENT_API_VERSION; use ankaios_api::ank_base::ConfigHash; @@ -1112,7 +1112,7 @@ mod tests { } pub fn generate_test_value_object() -> Value { - serde_yaml::from_str( + yaml_serde::from_str( r#" A: AA: aaa @@ -1125,7 +1125,7 @@ mod tests { } pub fn generate_test_value_object_for_wildcards() -> Value { - serde_yaml::from_str( + yaml_serde::from_str( r#" A: a: diff --git a/examples/rust_sdk_hello/Cargo.toml b/examples/rust_sdk_hello/Cargo.toml index 77e272cab..2e0d45fbb 100644 --- a/examples/rust_sdk_hello/Cargo.toml +++ b/examples/rust_sdk_hello/Cargo.toml @@ -9,7 +9,7 @@ chrono = "0.4" # required for encoding/decoding of protobuf varint format prost = "0.14" serde = { version = "1.0", features = ["derive"] } -serde_yaml = "0.9" +yaml_serde = "0.10" log = "0.4" env_logger = "0.11" tokio = { version = "1.41", features = [ diff --git a/examples/rust_sdk_logging/Cargo.toml b/examples/rust_sdk_logging/Cargo.toml index dbe2b35c3..0293ac812 100644 --- a/examples/rust_sdk_logging/Cargo.toml +++ b/examples/rust_sdk_logging/Cargo.toml @@ -9,7 +9,7 @@ chrono = "0.4" # required for encoding/decoding of protobuf varint format prost = "0.14" serde = { version = "1.0", features = ["derive"] } -serde_yaml = "0.9" +yaml_serde = "0.10" log = "0.4" env_logger = "0.11" tokio = { version = "1.41", features = [ diff --git a/server/Cargo.toml b/server/Cargo.toml index 7a624436a..fbeaeebe1 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -23,7 +23,7 @@ tokio = { version = "1.41", features = [ "process", ] } serde = { version = "1.0", features = ["derive"] } -serde_yaml = "0.9" +yaml_serde = "0.10" clap = { version = "4.5", features = ["derive", "env"] } handlebars = "6.1.0" toml = "0.9" diff --git a/server/src/ankaios_server/event_handler.rs b/server/src/ankaios_server/event_handler.rs index c2b82f0f4..e47e87594 100644 --- a/server/src/ankaios_server/event_handler.rs +++ b/server/src/ankaios_server/event_handler.rs @@ -28,7 +28,7 @@ use common::{ use ankaios_api::ank_base::{AgentMapSpec, CompleteStateRequest, WorkloadStatesMapSpec}; use std::collections::HashMap; -use serde_yaml::Value; +use yaml_serde::Value; #[cfg(test)] use mockall::automock; @@ -215,12 +215,12 @@ impl EventHandler { // [impl->swdd~event-handler-creates-altered-fields-using-full-difference-tree~1] // [impl->swdd~event-handler-creates-altered-fields-using-full-difference-tree-for-updated-fields~1] fn create_altered_fields_matching_subscriber_masks( - extended_difference_tree: &serde_yaml::Mapping, - difference_tree: &serde_yaml::Mapping, + extended_difference_tree: &yaml_serde::Mapping, + difference_tree: &yaml_serde::Mapping, subscriber_field_masks: &[Path], ) -> Vec { let mut altered_field_masks = Vec::new(); - let empty_mapping = serde_yaml::Mapping::new(); + let empty_mapping = yaml_serde::Mapping::new(); // [impl->swdd~event-handler-uses-first-difference-tree-upon-empty-subscriber-mask~1] if subscriber_field_masks.is_empty() { @@ -260,7 +260,7 @@ fn create_altered_fields_matching_subscriber_masks( match ( current_node .get(next_extended_key) - .unwrap_or(&serde_yaml::Value::Null), + .unwrap_or(&yaml_serde::Value::Null), next_extended_tree_level, ) { (Value::Mapping(next_node), Value::Mapping(next_extended_node)) => { @@ -806,11 +806,11 @@ mod tests { child2: null "#; - let first_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(first_difference_tree_yaml).unwrap(); + let first_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(first_difference_tree_yaml).unwrap(); - let full_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(full_difference_tree_yaml).unwrap(); + let full_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(full_difference_tree_yaml).unwrap(); // case 1: without wildcard let altered_fields = create_altered_fields_matching_subscriber_masks( @@ -856,11 +856,11 @@ mod tests { child2: null "#; - let first_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(first_difference_tree_yaml).unwrap(); + let first_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(first_difference_tree_yaml).unwrap(); - let full_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(full_difference_tree_yaml).unwrap(); + let full_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(full_difference_tree_yaml).unwrap(); // case 1: without wildcard let altered_fields = create_altered_fields_matching_subscriber_masks( @@ -901,11 +901,11 @@ mod tests { child2: null "#; - let first_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(first_difference_tree_yaml).unwrap(); + let first_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(first_difference_tree_yaml).unwrap(); - let full_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(full_difference_tree_yaml).unwrap(); + let full_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(full_difference_tree_yaml).unwrap(); // case 1: without wildcard let altered_fields = create_altered_fields_matching_subscriber_masks( @@ -943,11 +943,11 @@ mod tests { grandchild1: null "#; - let first_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(first_difference_tree_yaml).unwrap(); + let first_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(first_difference_tree_yaml).unwrap(); - let full_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(full_difference_tree_yaml).unwrap(); + let full_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(full_difference_tree_yaml).unwrap(); let altered_fields = create_altered_fields_matching_subscriber_masks( &full_difference_tree, @@ -978,11 +978,11 @@ mod tests { great_grandchild: null "#; - let first_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(first_difference_tree_yaml).unwrap(); + let first_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(first_difference_tree_yaml).unwrap(); - let full_difference_tree: serde_yaml::Mapping = - serde_yaml::from_str(full_difference_tree_yaml).unwrap(); + let full_difference_tree: yaml_serde::Mapping = + yaml_serde::from_str(full_difference_tree_yaml).unwrap(); // empty subscriber masks should return all paths from the first difference tree let altered_fields = create_altered_fields_matching_subscriber_masks( diff --git a/server/src/ankaios_server/server_state.rs b/server/src/ankaios_server/server_state.rs index 23d0b7b40..ea666645d 100644 --- a/server/src/ankaios_server/server_state.rs +++ b/server/src/ankaios_server/server_state.rs @@ -266,7 +266,7 @@ impl ServerState { } } - return_state.try_into().map_err(|err: serde_yaml::Error| { + return_state.try_into().map_err(|err: yaml_serde::Error| { format!("The result for CompleteState is invalid: '{err}'") }) } else { diff --git a/server/src/ankaios_server/state_comparator.rs b/server/src/ankaios_server/state_comparator.rs index de7aa6efd..393fd65bb 100644 --- a/server/src/ankaios_server/state_comparator.rs +++ b/server/src/ankaios_server/state_comparator.rs @@ -17,7 +17,7 @@ use common::{ state_manipulation::{Object, Path}, std_extensions::{IllegalStateResult, UnreachableOption}, }; -use serde_yaml::{Mapping, Value}; +use yaml_serde::{Mapping, Value}; use std::collections::HashSet; #[cfg(test)] @@ -40,13 +40,13 @@ pub enum StackTask<'a> { impl StateComparator { pub fn new(old_state_spec: CompleteStateSpec, new_state_spec: CompleteStateSpec) -> Self { let old_state = - serde_yaml::to_value(ankaios_api::ank_base::CompleteState::from(old_state_spec)) + yaml_serde::to_value(ankaios_api::ank_base::CompleteState::from(old_state_spec)) .unwrap_or_illegal_state() .as_mapping() .unwrap_or_unreachable() .to_owned(); let new_state = - serde_yaml::to_value(ankaios_api::ank_base::CompleteState::from(new_state_spec)) + yaml_serde::to_value(ankaios_api::ank_base::CompleteState::from(new_state_spec)) .unwrap_or_illegal_state() .as_mapping() .unwrap_or_unreachable() @@ -99,7 +99,7 @@ impl StateComparator { StateDifferenceTree::insert_path( &mut state_difference_tree.added_tree.first_difference_tree, Path::from(added_field_mask.clone()), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); StateDifferenceTree::insert_path( @@ -123,7 +123,7 @@ impl StateComparator { StateDifferenceTree::insert_path( &mut state_difference_tree.removed_tree.first_difference_tree, Path::from(removed_field_mask.clone()), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); StateDifferenceTree::insert_path( @@ -170,7 +170,7 @@ impl StateComparator { .added_tree .first_difference_tree, Path::from(sequence_field_mask), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); } else if !old_state_sequence.is_empty() && new_state_sequence.is_empty() @@ -180,7 +180,7 @@ impl StateComparator { .removed_tree .first_difference_tree, Path::from(sequence_field_mask), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); } else if old_state_sequence != new_state_sequence { StateDifferenceTree::insert_path( @@ -188,7 +188,7 @@ impl StateComparator { .updated_tree .full_difference_tree, Path::from(sequence_field_mask), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); } } @@ -201,7 +201,7 @@ impl StateComparator { .updated_tree .full_difference_tree, Path::from(updated_field_mask), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); } } @@ -307,7 +307,7 @@ impl StateDifferenceTree { #[cfg(test)] pub fn generate_difference_tree_from_paths(new_tree_paths: &[Vec]) -> Object { - let mut mapping = serde_yaml::Mapping::new(); + let mut mapping = yaml_serde::Mapping::new(); for tree_path in new_tree_paths { let mut current_map = &mut mapping; let last = tree_path.last(); @@ -317,7 +317,7 @@ pub fn generate_difference_tree_from_paths(new_tree_paths: &[Vec]) -> Ob } current_map.insert( Value::String(part.clone()), - serde_yaml::Value::Mapping(serde_yaml::Mapping::new()), + yaml_serde::Value::Mapping(yaml_serde::Mapping::new()), ); current_map = current_map .get_mut(Value::String(part.to_owned())) @@ -326,10 +326,10 @@ pub fn generate_difference_tree_from_paths(new_tree_paths: &[Vec]) -> Ob .unwrap_or_unreachable(); } if let Some(last) = last { - current_map.insert(Value::String(last.to_owned()), serde_yaml::Value::Null); + current_map.insert(Value::String(last.to_owned()), yaml_serde::Value::Null); } } - Object::from(serde_yaml::Value::Mapping(mapping)) + Object::from(yaml_serde::Value::Mapping(mapping)) } // [utest->swdd~server-calculates-state-differences~1] @@ -350,14 +350,14 @@ mod tests { let state_comparator = StateComparator::new(old_state_spec.clone(), new_state_spec.clone()); let expected_new_state = - serde_yaml::to_value(ankaios_api::ank_base::CompleteState::from(new_state_spec)) + yaml_serde::to_value(ankaios_api::ank_base::CompleteState::from(new_state_spec)) .unwrap() .as_mapping() .unwrap() .to_owned(); let expected_old_state = - serde_yaml::to_value(ankaios_api::ank_base::CompleteState::from(old_state_spec)) + yaml_serde::to_value(ankaios_api::ank_base::CompleteState::from(old_state_spec)) .unwrap() .as_mapping() .unwrap() @@ -388,7 +388,7 @@ mod tests { key_1_2: value_1_2 "#; - let old_state: Mapping = serde_yaml::from_str(old_state_yaml).unwrap(); + let old_state: Mapping = yaml_serde::from_str(old_state_yaml).unwrap(); let state_comparator = StateComparator { old_state: old_state.clone(), @@ -417,8 +417,8 @@ mod tests { "#; let state_comparator = StateComparator { - old_state: serde_yaml::from_str(old_state_yaml).unwrap(), - new_state: serde_yaml::from_str(new_state_yaml).unwrap(), + old_state: yaml_serde::from_str(old_state_yaml).unwrap(), + new_state: yaml_serde::from_str(new_state_yaml).unwrap(), }; let state_difference_tree = state_comparator.state_differences(); @@ -436,12 +436,12 @@ mod tests { key_1_2: null "#; let expected_first_level_difference_tree = Object::from( - serde_yaml::from_str::(expected_first_level_change_tree_yaml) + yaml_serde::from_str::(expected_first_level_change_tree_yaml) .unwrap(), ); let expected_full_difference_tree = Object::from( - serde_yaml::from_str::(expected_full_change_tree_yaml).unwrap(), + yaml_serde::from_str::(expected_full_change_tree_yaml).unwrap(), ); assert_eq!( @@ -471,8 +471,8 @@ mod tests { "#; let state_comparator = StateComparator { - old_state: serde_yaml::from_str(old_state_yaml).unwrap(), - new_state: serde_yaml::from_str(new_state_yaml).unwrap(), + old_state: yaml_serde::from_str(old_state_yaml).unwrap(), + new_state: yaml_serde::from_str(new_state_yaml).unwrap(), }; let state_difference_tree = state_comparator.state_differences(); @@ -482,7 +482,7 @@ mod tests { key_2_1: null "#; let expected_updated_tree = Object::from( - serde_yaml::from_str::(expected_updated_tree_yaml).unwrap(), + yaml_serde::from_str::(expected_updated_tree_yaml).unwrap(), ); assert_eq!( @@ -510,8 +510,8 @@ mod tests { "#; let state_comparator = StateComparator { - old_state: serde_yaml::from_str(old_state_yaml).unwrap(), - new_state: serde_yaml::from_str(new_state_yaml).unwrap(), + old_state: yaml_serde::from_str(old_state_yaml).unwrap(), + new_state: yaml_serde::from_str(new_state_yaml).unwrap(), }; let state_difference_tree = state_comparator.state_differences(); @@ -522,7 +522,7 @@ mod tests { key_3_1: null "#; let expected_removed_first_difference_tree = Object::from( - serde_yaml::from_str::(expected_removed_first_difference_tree_yaml) + yaml_serde::from_str::(expected_removed_first_difference_tree_yaml) .unwrap(), ); @@ -534,7 +534,7 @@ mod tests { "#; let expected_removed_full_difference_tree = Object::from( - serde_yaml::from_str::(expected_removed_full_difference_tree_yaml) + yaml_serde::from_str::(expected_removed_full_difference_tree_yaml) .unwrap(), ); @@ -563,8 +563,8 @@ mod tests { "#; let state_comparator = StateComparator { - old_state: serde_yaml::from_str(old_state_yaml).unwrap(), - new_state: serde_yaml::from_str(new_state_yaml).unwrap(), + old_state: yaml_serde::from_str(old_state_yaml).unwrap(), + new_state: yaml_serde::from_str(new_state_yaml).unwrap(), }; let state_difference_tree = state_comparator.state_differences(); @@ -572,7 +572,7 @@ mod tests { key_1_1: null "#; let expected_removed_tree = Object::from( - serde_yaml::from_str::(expected_removed_tree_yaml).unwrap(), + yaml_serde::from_str::(expected_removed_tree_yaml).unwrap(), ); assert_eq!( @@ -599,8 +599,8 @@ mod tests { "#; let state_comparator = StateComparator { - old_state: serde_yaml::from_str(old_state_yaml).unwrap(), - new_state: serde_yaml::from_str(new_state_yaml).unwrap(), + old_state: yaml_serde::from_str(old_state_yaml).unwrap(), + new_state: yaml_serde::from_str(new_state_yaml).unwrap(), }; let state_difference_tree = state_comparator.state_differences(); @@ -609,7 +609,7 @@ mod tests { key_1: null "#; let expected_added_tree = Object::from( - serde_yaml::from_str::(expected_added_tree_yaml).unwrap(), + yaml_serde::from_str::(expected_added_tree_yaml).unwrap(), ); assert_eq!( @@ -638,8 +638,8 @@ mod tests { "#; let state_comparator = StateComparator { - old_state: serde_yaml::from_str(old_state_yaml).unwrap(), - new_state: serde_yaml::from_str(new_state_yaml).unwrap(), + old_state: yaml_serde::from_str(old_state_yaml).unwrap(), + new_state: yaml_serde::from_str(new_state_yaml).unwrap(), }; let state_difference_tree = state_comparator.state_differences(); @@ -648,7 +648,7 @@ mod tests { key_1: null "#; let expected_updated_tree = Object::from( - serde_yaml::from_str::(expected_updated_tree_yaml).unwrap(), + yaml_serde::from_str::(expected_updated_tree_yaml).unwrap(), ); assert_eq!( @@ -675,8 +675,8 @@ mod tests { "#; let state_comparator = StateComparator { - old_state: serde_yaml::from_str(old_state_yaml).unwrap(), - new_state: serde_yaml::from_str(new_state_yaml).unwrap(), + old_state: yaml_serde::from_str(old_state_yaml).unwrap(), + new_state: yaml_serde::from_str(new_state_yaml).unwrap(), }; let state_difference_tree = state_comparator.state_differences(); @@ -687,7 +687,7 @@ mod tests { key_3: null "#; let expected_removed_tree = Object::from( - serde_yaml::from_str::(expected_removed_tree_yaml).unwrap(), + yaml_serde::from_str::(expected_removed_tree_yaml).unwrap(), ); assert_eq!( @@ -709,7 +709,7 @@ mod tests { "#; let state_comparator = StateComparator { - old_state: serde_yaml::from_str(old_state_yaml).unwrap(), + old_state: yaml_serde::from_str(old_state_yaml).unwrap(), new_state: Mapping::default(), }; @@ -719,7 +719,7 @@ mod tests { "20": null "#; let expected_removed_tree = Object::from( - serde_yaml::from_str::(expected_removed_tree_yaml).unwrap(), + yaml_serde::from_str::(expected_removed_tree_yaml).unwrap(), ); assert_eq!( state_difference_tree.removed_tree.first_difference_tree, @@ -741,7 +741,7 @@ mod tests { let state_comparator = StateComparator { old_state: Mapping::default(), - new_state: serde_yaml::from_str(old_state_yaml).unwrap(), + new_state: yaml_serde::from_str(old_state_yaml).unwrap(), }; let state_difference_tree = state_comparator.state_differences(); @@ -761,31 +761,31 @@ mod tests { StateDifferenceTree::insert_path( &mut state_difference_tree.added_tree.first_difference_tree, Path::from(path_to_first_change.clone()), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); StateDifferenceTree::insert_path( &mut state_difference_tree.added_tree.full_difference_tree, Path::from(full_change_path.clone()), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); StateDifferenceTree::insert_path( &mut state_difference_tree.removed_tree.first_difference_tree, Path::from(path_to_first_change), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); StateDifferenceTree::insert_path( &mut state_difference_tree.removed_tree.full_difference_tree, Path::from(full_change_path.clone()), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); StateDifferenceTree::insert_path( &mut state_difference_tree.updated_tree.full_difference_tree, Path::from(full_change_path), - serde_yaml::Value::Null, + yaml_serde::Value::Null, ); let expected_first_difference_tree_yaml = r#" @@ -793,7 +793,7 @@ mod tests { level_2: null "#; let expected_first_difference_tree = Object::from( - serde_yaml::from_str::(expected_first_difference_tree_yaml).unwrap(), + yaml_serde::from_str::(expected_first_difference_tree_yaml).unwrap(), ); let expected_full_difference_tree_yaml = r#" @@ -802,7 +802,7 @@ mod tests { level_3: null "#; let expected_full_difference_tree = Object::from( - serde_yaml::from_str::(expected_full_difference_tree_yaml).unwrap(), + yaml_serde::from_str::(expected_full_difference_tree_yaml).unwrap(), ); assert_eq!( @@ -835,7 +835,7 @@ mod tests { let empty_tree = StateDifferenceTree { added_tree: AddedTree::default(), removed_tree: RemovedTree { - first_difference_tree: Object::from(serde_yaml::Value::Mapping(Mapping::new())), + first_difference_tree: Object::from(yaml_serde::Value::Mapping(Mapping::new())), full_difference_tree: Object::default(), }, updated_tree: UpdatedTree::default(), @@ -844,12 +844,12 @@ mod tests { assert!(empty_tree.is_empty()); let new_tree = Mapping::from_iter([( - serde_yaml::Value::String("key".to_owned()), - serde_yaml::Value::String("value".to_owned()), + yaml_serde::Value::String("key".to_owned()), + yaml_serde::Value::String("value".to_owned()), )]); let non_empty_tree = StateDifferenceTree { added_tree: AddedTree { - first_difference_tree: Object::from(serde_yaml::Value::Mapping(new_tree)), + first_difference_tree: Object::from(yaml_serde::Value::Mapping(new_tree)), full_difference_tree: Object::default(), }, removed_tree: RemovedTree::default(), diff --git a/server/src/main.rs b/server/src/main.rs index f0047ad38..8d76c7c89 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -32,8 +32,8 @@ pub mod test_helper; // [impl->swdd~server-validates-startup-manifest-tags-format~1] fn validate_tags_format_in_manifest(data: &str) -> Result<(), String> { - let yaml_value: serde_yaml::Value = - serde_yaml::from_str(data).map_err(|e| format!("Failed to parse YAML: {e}"))?; + let yaml_value: yaml_serde::Value = + yaml_serde::from_str(data).map_err(|e| format!("Failed to parse YAML: {e}"))?; let api_version = yaml_value .get("apiVersion") @@ -90,7 +90,7 @@ async fn main() { // [impl->swdd~server-state-in-memory~1] // [impl->swdd~server-loads-startup-state-file~3] - let state: StateSpec = serde_yaml::from_str(&data) + let state: StateSpec = yaml_serde::from_str(&data) .unwrap_or_exit("Parsing start config failed with error"); log::trace!( "The state is initialized with the following workloads: {:?}", diff --git a/tests/resources/control_interface_tester/Cargo.toml b/tests/resources/control_interface_tester/Cargo.toml index 2286fb553..a4d173f37 100644 --- a/tests/resources/control_interface_tester/Cargo.toml +++ b/tests/resources/control_interface_tester/Cargo.toml @@ -12,5 +12,5 @@ chrono = "0.4" # required for encoding/decoding of protobuf varint format prost = "0.14" serde = "1.0" -serde_yaml = "0.9" +yaml_serde = "0.10" serde_json = "1.0.121" diff --git a/tests/resources/control_interface_tester/src/main.rs b/tests/resources/control_interface_tester/src/main.rs index 469fe34e4..8d7b23d2e 100644 --- a/tests/resources/control_interface_tester/src/main.rs +++ b/tests/resources/control_interface_tester/src/main.rs @@ -184,7 +184,7 @@ fn main() { exit(1); }); - let commands: Vec = serde_yaml::from_reader(commands_json).unwrap_or_else(|err| { + let commands: Vec = yaml_serde::from_reader(commands_json).unwrap_or_else(|err| { logging::log(&format!("Could not parse commands argument: '{err}'")); exit(1) }); @@ -767,6 +767,6 @@ fn read_yaml_file(path: &Path) -> Result { let file = File::open(path) .map_err(|err| format!("Error: cannot open '{}': '{}'", path.to_str().unwrap(), err))?; - serde_yaml::from_reader(file) + yaml_serde::from_reader(file) .map_err(|err| format!("Could not parse '{}': {}", path.to_str().unwrap(), err)) } From 1e0014f4971334768ac3b99001be3c47816fbc7e Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Tue, 13 Oct 2026 10:39:36 +0200 Subject: [PATCH 51/55] Remove orphaned storage in stest cleanup --- tests/resources/ankaios.resource | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/resources/ankaios.resource b/tests/resources/ankaios.resource index 52decdc38..642ff62b8 100644 --- a/tests/resources/ankaios.resource +++ b/tests/resources/ankaios.resource @@ -331,9 +331,13 @@ Ankaios agent with name "${name}" is terminated Podman has deleted all existing containers Run Process podman rm --all --force --time 2 timeout=20 shell=True + # Removes orphaned container storage + Run Process podman rm --storage --all timeout=20 shell=True Podman has deleted all existing pods Run Process podman pod rm --all --force --time 2 timeout=20 shell=True + # Removes orphaned container storage left behind by pod member containers + Run Process podman rm --storage --all timeout=20 shell=True Podman has deleted all existing volumes Run Process podman volume rm --all --force timeout=20 shell=True From 8cd7858eae2908fa94bf7485cdeabaf57cbf6481 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Tue, 13 Oct 2026 12:47:56 +0200 Subject: [PATCH 52/55] Update dependencies Excluded pest from the update, as version 2.9.3 is not working with statically compiled code. --- Cargo.lock | 227 +++++++++++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8e41d295..573d94d74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,7 +216,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.20", + "thiserror 2.0.21", "time", ] @@ -251,7 +251,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -377,9 +377,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.4.6" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" dependencies = [ "find-msvc-tools", "shlex", @@ -387,9 +387,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cfg_aliases" @@ -464,7 +464,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -646,7 +646,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] @@ -665,9 +665,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" [[package]] name = "derive_builder" @@ -746,7 +746,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -772,9 +772,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" [[package]] name = "email_address" @@ -830,7 +830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -852,9 +852,9 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "fixedbitset" @@ -942,7 +942,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -1045,9 +1045,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" dependencies = [ "atomic-waker", "bytes", @@ -1075,7 +1075,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.20", + "thiserror 2.0.21", ] [[package]] @@ -1169,9 +1169,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" dependencies = [ "atomic-waker", "bytes", @@ -1204,15 +1204,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", + "httparse", "hyper", "libc", "pin-project-lite", @@ -1407,9 +1408,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.37" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" dependencies = [ "defmt", "jiff-core", @@ -1432,9 +1433,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.37" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" dependencies = [ "jiff-core", "proc-macro2", @@ -1444,9 +1445,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.105" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -1482,15 +1483,15 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "libc" -version = "0.2.189" +version = "0.2.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" [[package]] name = "libyaml-rs" @@ -1512,9 +1513,9 @@ checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "litrs" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70" [[package]] name = "lock_api" @@ -1557,9 +1558,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "log", @@ -1715,9 +1716,9 @@ dependencies = [ [[package]] name = "num-modular" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8e500409e6cd603b03e477c26a6caecdc27ac58979a53e881c75eafc079f44" +checksum = "26ac76200f74e658124f95fa63e1a82b2fd2181c5b2fdde80b3d89d2d3f905e7" [[package]] name = "num-order" @@ -1948,9 +1949,9 @@ dependencies = [ [[package]] name = "powerfmt" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" [[package]] name = "predicates" @@ -2089,9 +2090,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-to-cmark" -version = "22.0.1" +version = "22.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60" +checksum = "84bbb29c624230c4bd1047bbdb2aa47e41c860e9665ce62ba9504eebe91bf867" dependencies = [ "pulldown-cmark", ] @@ -2119,9 +2120,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ "chacha20", "getrandom 0.4.3", @@ -2160,7 +2161,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -2241,15 +2242,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2301,9 +2302,9 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "schemars" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" dependencies = [ "dyn-clone", "ref-cast", @@ -2314,14 +2315,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +checksum = "e1c3a92094fa7d61aa124645844facb6b554dfc797136d0f5fd1f890e2bffc69" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -2363,7 +2364,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -2374,14 +2375,14 @@ checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -2392,9 +2393,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" dependencies = [ "serde_core", ] @@ -2468,9 +2469,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.16.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" [[package]] name = "socket2" @@ -2533,9 +2534,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.5" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -2567,7 +2568,7 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -2617,7 +2618,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2637,11 +2638,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl 2.0.20", + "thiserror-impl 2.0.21", ] [[package]] @@ -2657,13 +2658,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] @@ -2708,9 +2709,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.53.1" +version = "1.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" dependencies = [ "bytes", "libc", @@ -2730,14 +2731,14 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] name = "tokio-rustls" -version = "0.26.5" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ "rustls", "tokio", @@ -2756,9 +2757,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" dependencies = [ "bytes", "futures-core", @@ -2794,18 +2795,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ "winnow 1.0.4", ] [[package]] name = "toml_writer" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" [[package]] name = "tonic" @@ -2967,9 +2968,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f" [[package]] name = "unicode-general-category" @@ -2979,9 +2980,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-segmentation" @@ -3015,9 +3016,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.26.1" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -3049,9 +3050,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "want" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" dependencies = [ "try-lock", ] @@ -3073,9 +3074,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -3086,9 +3087,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3096,22 +3097,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.128" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] @@ -3367,7 +3368,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror 2.0.20", + "thiserror 2.0.21", "time", ] @@ -3397,30 +3398,30 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", "synstructure 0.14.0", ] [[package]] name = "zerocopy" -version = "0.8.57" +version = "0.8.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" +checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.57" +version = "0.8.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" +checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" dependencies = [ "proc-macro2", "quote", @@ -3444,15 +3445,15 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", "synstructure 0.14.0", ] [[package]] name = "zeroize" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" [[package]] name = "zerotrie" @@ -3484,7 +3485,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.9", ] [[package]] From b5f6a6ce09a48a11c677afda456ce3b6b30d292b Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:59:38 +0200 Subject: [PATCH 53/55] Update tabled --- Cargo.lock | 65 +++++++++++-------------------- ank/Cargo.toml | 2 +- ank/src/cli_commands/cli_table.rs | 10 ++--- deny.toml | 8 ---- 4 files changed, 29 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 573d94d74..96b6cd6f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,7 +461,7 @@ version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 3.0.9", @@ -830,7 +830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1104,12 +1104,6 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -1806,9 +1800,9 @@ checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] name = "papergrid" -version = "0.13.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b0f8def1f117e13c895f3eda65a7b5650688da29d6ad04635f61bc7b92eebd" +checksum = "d0984e668274d34691bc2b262ef0d115de5fa9973bcdee7ae32213f93099153e" dependencies = [ "bytecount", "fnv", @@ -1993,28 +1987,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -2040,7 +2012,7 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ - "heck 0.5.0", + "heck", "itertools 0.14.0", "log", "multimap", @@ -2250,7 +2222,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2587,25 +2559,25 @@ dependencies = [ [[package]] name = "tabled" -version = "0.17.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6709222f3973137427ce50559cd564dc187a95b9cfe01613d2f4e93610e510a" +checksum = "2d2596a104db1900b943f97d793a6c99addca918c4525c999f751a0f17d472eb" dependencies = [ "papergrid", "tabled_derive", + "testing_table", ] [[package]] name = "tabled_derive" -version = "0.9.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931be476627d4c54070a1f3a9739ccbfec9b36b39815106a20cce2243bbcefe1" +checksum = "7dca1937322a1e892b1a65f6a6736183bb0a29d3b4234d1d53bc436dff9beb76" dependencies = [ - "heck 0.4.1", - "proc-macro-error2", + "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.119", ] [[package]] @@ -2618,7 +2590,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2627,6 +2599,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "testing_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/ank/Cargo.toml b/ank/Cargo.toml index f59dc0928..b1231b7e5 100644 --- a/ank/Cargo.toml +++ b/ank/Cargo.toml @@ -52,7 +52,7 @@ tokio = { version = "1.41", features = [ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" yaml_serde = "0.10" -tabled = "0.17" +tabled = "0.22" tempfile = "3.17" toml = "0.9" uuid = { version = "1.7.0", features = ["v4"] } diff --git a/ank/src/cli_commands/cli_table.rs b/ank/src/cli_commands/cli_table.rs index f15e68d1e..ec853f464 100644 --- a/ank/src/cli_commands/cli_table.rs +++ b/ank/src/cli_commands/cli_table.rs @@ -23,7 +23,7 @@ fn terminal_width() -> usize { use common::std_extensions::UnreachableOption; use tabled::{ Table, Tabled, - grid::config::Entity, + grid::config::Position, settings::{Modify, Padding, Style, Width, object::Columns}, }; @@ -89,7 +89,7 @@ where let available_column_width = self.column_width_for_terminal(column_position)?; self.table.with( - Modify::new(Columns::single(column_position)).with(Width::wrap(available_column_width)), + Modify::new(Columns::one(column_position)).with(Width::wrap(available_column_width)), ); Ok(self.table.to_string()) } @@ -104,7 +104,7 @@ where let available_column_width = self.column_width_for_terminal(column_position)?; self.table.with( - Modify::new(Columns::single(column_position)).with( + Modify::new(Columns::one(column_position)).with( Width::truncate(available_column_width).suffix(Self::TRUNCATED_COLUMN_SUFFIX), ), ); @@ -121,7 +121,7 @@ where let first_column_default_padding = self .table .get_config() - .get_padding(Entity::Column(Self::FIRST_COLUMN_POS)); + .get_padding(Position::new(0, Self::FIRST_COLUMN_POS)); /* Set the left padding of the first and the right padding of the last column to zero to align the table content to the full terminal width for better output quality. */ @@ -136,7 +136,7 @@ where let last_column_default_padding = self .table .get_config() - .get_padding(Entity::Column(last_column_pos)); + .get_padding(Position::new(0, last_column_pos)); self.table .with(Modify::new(Columns::last()).with(Padding::new( diff --git a/deny.toml b/deny.toml index 99a1913b7..a59257b5c 100644 --- a/deny.toml +++ b/deny.toml @@ -25,13 +25,5 @@ license-files = [ { path = "LICENSE", hash = 0xbd0eed23 } ] -[advisories] -ignore = [ - # proc-macro-error2 is used by tabled_derive (dependency of tabled). - # The advisory confirms no safe upgrade is available as of the latest tabled release. - # A fix is already available in tabled, but the release would be delayed: https://github.com/zhiburt/tabled/pull/584#issuecomment-4778335205 - { id = "RUSTSEC-2026-0173", reason = "tabled_derive depends on proc-macro-error2 and no safe upgrade is available upstream" }, -] - [bans] multiple-versions = "allow" From c896ee9e6f0a2bd916d223550c3a1039ffecf765 Mon Sep 17 00:00:00 2001 From: Christoph Hamm <130038849+christoph-hamm@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:03:27 +0200 Subject: [PATCH 54/55] Update Werkzeug version in vehicle-signal tutorial --- doc/docs/usage/tutorial-vehicle-signals.md | 6 +++--- .../vehicle_signals/speed-provider/requirements.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/docs/usage/tutorial-vehicle-signals.md b/doc/docs/usage/tutorial-vehicle-signals.md index c652781c7..c8a2dffdd 100644 --- a/doc/docs/usage/tutorial-vehicle-signals.md +++ b/doc/docs/usage/tutorial-vehicle-signals.md @@ -76,7 +76,7 @@ workloads: runtime: podman agent: agent_A runtimeConfig: | - image: ghcr.io/eclipse-ankaios/speed-provider:0.1.3 + image: ghcr.io/eclipse-ankaios/speed-provider:0.1.4 commandOptions: - "--net=host" ``` @@ -225,7 +225,7 @@ workloads: runtime: podman agent: agent_A runtimeConfig: | - image: ghcr.io/eclipse-ankaios/speed-provider:0.1.3 + image: ghcr.io/eclipse-ankaios/speed-provider:0.1.4 commandOptions: - "--net=host" - "-e" @@ -281,7 +281,7 @@ workloads: dependencies: databroker: ADD_COND_RUNNING runtimeConfig: | - image: ghcr.io/eclipse-ankaios/speed-provider:0.1.3 + image: ghcr.io/eclipse-ankaios/speed-provider:0.1.4 commandOptions: - "--net=host" - "-e" diff --git a/tools/tutorials/vehicle_signals/speed-provider/requirements.txt b/tools/tutorials/vehicle_signals/speed-provider/requirements.txt index c1aa73b34..6a2924e89 100644 --- a/tools/tutorials/vehicle_signals/speed-provider/requirements.txt +++ b/tools/tutorials/vehicle_signals/speed-provider/requirements.txt @@ -1,3 +1,3 @@ kuksa-client==0.4.1 Flask==3.1.2 -Werkzeug==3.1.5 +Werkzeug==3.1.9 From 80353e5d0e302b984529c665cd9ea8644b373598 Mon Sep 17 00:00:00 2001 From: GabyUnalaq Date: Tue, 13 Oct 2026 11:51:45 +0000 Subject: [PATCH 55/55] Apply fix for flaky grpc tests --- grpc/tests/grpc_test.rs | 174 +++++++++++++++++++++++----------------- 1 file changed, 99 insertions(+), 75 deletions(-) diff --git a/grpc/tests/grpc_test.rs b/grpc/tests/grpc_test.rs index fc0eae8cb..feb1b88b0 100644 --- a/grpc/tests/grpc_test.rs +++ b/grpc/tests/grpc_test.rs @@ -37,6 +37,7 @@ mod grpc_tests { collections::HashMap, fs::File, io::{self, Write}, + net::TcpListener, os::unix::fs::PermissionsExt, path::PathBuf, time::Duration, @@ -44,8 +45,12 @@ mod grpc_tests { use hyper_util::rt::TokioIo; use tempfile::TempDir; - use tokio::net::UnixStream; - use tokio::{sync::mpsc, task::JoinHandle, time::timeout}; + use tokio::{ + net::{TcpStream, UnixStream}, + sync::mpsc, + task::JoinHandle, + time::timeout, + }; use tokio_stream::wrappers::ReceiverStream; use tonic::transport::{Channel, Endpoint}; use tower::service_fn; @@ -214,6 +219,77 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb Agent, } + // Lets the OS pick a currently free port. Hardcoded ports lie inside the ephemeral port + // range and can be occupied by unrelated outgoing connections on busy machines (e.g. CI), + // which makes the server bind fail and the test fail sporadically. + fn get_free_port() -> u16 { + TcpListener::bind("0.0.0.0:0") + .and_then(|listener| listener.local_addr()) + .map(|addr| addr.port()) + .expect("Could not get a free port from the OS.") + } + + // Waits until the gRPC server accepts TCP connections. Returns false if the server + // stopped before, e.g. because binding the port failed. + async fn wait_for_grpc_server( + port: u16, + grpc_server_task: &JoinHandle>, + ) -> bool { + for _ in 0..100 { + if grpc_server_task.is_finished() { + return false; + } + if TcpStream::connect(("127.0.0.1", port)).await.is_ok() { + return true; + } + tokio::time::sleep(Duration::from_millis(50)).await; + } + panic!("The test gRPC server on port '{port}' did not start in time."); + } + + // Starts a gRPC communication server on a free port and waits until it accepts connections. + // Clients must not be started before the server is ready: the CLI client does not retry and + // drops the FromServer channel on failure, which shuts down the server. + // As the free port can be taken by another socket before the server binds it, the start is + // retried on a new port. + // Returns the port and the receiver on which the server forwards the received ToServer + // messages. The returned FromServerSender must be kept alive for the whole test, otherwise + // the server shuts down as soon as its FromServer channel is closed. + async fn start_test_grpc_server( + tls_config: Option, + ) -> ( + u16, + ToServerReceiver, + FromServerSender, + JoinHandle>, + ) { + const MAX_START_ATTEMPTS: usize = 5; + for _ in 0..MAX_START_ATTEMPTS { + let port = get_free_port(); + let (to_grpc_server, grpc_server_receiver) = mpsc::channel::(20); + let (to_server, server_receiver) = mpsc::channel::(20); + + let mut communications_server = + GRPCCommunicationsServer::new(to_server, tls_config.clone()); + let socket_addr = ServerConnection::Tcp(format!("0.0.0.0:{port}").parse().unwrap()); + + let grpc_server_task = tokio::spawn(async move { + communications_server + .start(grpc_server_receiver, socket_addr) + .await + }); + + if wait_for_grpc_server(port, &grpc_server_task).await { + return (port, server_receiver, to_grpc_server, grpc_server_task); + } + log::warn!( + "Test gRPC server on port '{port}' stopped: '{:?}'. Retrying on another port.", + grpc_server_task.await + ); + } + panic!("Could not start the test gRPC server after {MAX_START_ATTEMPTS} attempts."); + } + async fn generate_test_grpc_communication_client( server_addr: &str, comm_type: &CommunicationType, @@ -250,7 +326,6 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb } async fn generate_test_grpc_communication_setup( - port: u16, comm_type: CommunicationType, test_request_id: &str, tls_pem_files_package: Option<&TestPEMFilesPackage>, @@ -269,10 +344,6 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - let server_addr = format!("0.0.0.0:{port}"); - let (to_grpc_server, grpc_server_receiver) = mpsc::channel::(20); - let (to_server, server_receiver) = mpsc::channel::(20); - let (server_tls_config, agent_tls_config, cli_tls_config) = if let Some(tls_pem_files_package) = tls_pem_files_package { ( @@ -285,15 +356,9 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb }; // create communication server - let mut communications_server = GRPCCommunicationsServer::new(to_server, server_tls_config); - - let socket_addr = ServerConnection::Tcp(server_addr.parse().unwrap()); - - let grpc_server_task = tokio::spawn(async move { - communications_server - .start(grpc_server_receiver, socket_addr) - .await - }); + let (port, server_receiver, to_grpc_server, grpc_server_task) = + start_test_grpc_server(server_tls_config).await; + let server_addr = format!("0.0.0.0:{port}"); // create communication client let (to_grpc_client, grpc_client_task) = generate_test_grpc_communication_client( @@ -327,7 +392,6 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb let (to_grpc_client, mut server_receiver, _grpc_server_task, _grpc_client_task) = generate_test_grpc_communication_setup( - 50050, CommunicationType::Cli, test_request_id, Some(&test_pem_files_package), @@ -372,13 +436,9 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb async fn itest_grpc_communication_client_cli_connection_grpc_server_received_request_complete_state() { let test_request_id = "test_request_id"; - let (to_grpc_client, mut server_receiver, _, _) = generate_test_grpc_communication_setup( - 50051, - CommunicationType::Cli, - test_request_id, - None, - ) - .await; + let (to_grpc_client, mut server_receiver, _, _) = + generate_test_grpc_communication_setup(CommunicationType::Cli, test_request_id, None) + .await; // send request to grpc client let request_complete_state_result = to_grpc_client @@ -415,13 +475,9 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb #[tokio::test(flavor = "multi_thread", worker_threads = 1)] // set worker_threads = 1 to solve the failing of the test on woodpecker async fn itest_grpc_communication_client_cli_connection_grpc_server_received_update_state() { let test_request_id = "test_request_id"; - let (to_grpc_client, mut server_receiver, _, _) = generate_test_grpc_communication_setup( - 50052, - CommunicationType::Cli, - test_request_id, - None, - ) - .await; + let (to_grpc_client, mut server_receiver, _, _) = + generate_test_grpc_communication_setup(CommunicationType::Cli, test_request_id, None) + .await; // send request to grpc client let update_state_result = to_grpc_client @@ -449,13 +505,9 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb #[tokio::test(flavor = "multi_thread", worker_threads = 1)] // set worker_threads = 1 to solve the failing of the test on woodpecker async fn itest_grpc_communication_client_agent_connection_grpc_server_received_agent_hello() { let test_agent_name = "test_agent_name"; - let (_, mut server_receiver, _, _) = generate_test_grpc_communication_setup( - 50053, - CommunicationType::Agent, - test_agent_name, - None, - ) - .await; + let (_, mut server_receiver, _, _) = + generate_test_grpc_communication_setup(CommunicationType::Agent, test_agent_name, None) + .await; let result = timeout(Duration::from_secs(10), server_receiver.recv()).await; @@ -477,7 +529,6 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb let test_pem_files_package = TestPEMFilesPackage::new().unwrap(); let (_, mut server_receiver, _, _) = generate_test_grpc_communication_setup( - 50054, CommunicationType::Agent, test_agent_name, Some(&test_pem_files_package), @@ -538,33 +589,6 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb ); } - // Starts an insecure gRPC communication server for testing the commander connection - // and returns the receiver on which the server forwards the received ToServer messages. - // The returned FromServerSender must be kept alive for the whole test, otherwise the - // server shuts down as soon as its FromServer channel is closed. - async fn start_insecure_test_grpc_server( - port: u16, - ) -> ( - ToServerReceiver, - FromServerSender, - JoinHandle>, - ) { - let server_addr = format!("0.0.0.0:{port}"); - let (to_grpc_server, grpc_server_receiver) = mpsc::channel::(20); - let (to_server, server_receiver) = mpsc::channel::(20); - - let mut communications_server = GRPCCommunicationsServer::new(to_server, None); - let socket_addr = ServerConnection::Tcp(server_addr.parse().unwrap()); - - let grpc_server_task = tokio::spawn(async move { - communications_server - .start(grpc_server_receiver, socket_addr) - .await - }); - - (server_receiver, to_grpc_server, grpc_server_task) - } - // Connects a raw CommandConnectionClient to the test server, retrying until the // server accepts connections. async fn connect_command_client_with_retry(url: String) -> CommandConnectionClient { @@ -586,8 +610,8 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb async fn itest_grpc_communication_command_connection_grpc_server_received_request_complete_state() { let test_request_id = "test_request_id"; - let (mut server_receiver, _to_grpc_server, _grpc_server_task) = - start_insecure_test_grpc_server(50055).await; + let (port, mut server_receiver, _to_grpc_server, _grpc_server_task) = + start_test_grpc_server(None).await; let (client_to_server_tx, grpc_client_receiver) = mpsc::channel::(20); @@ -600,7 +624,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb .unwrap(); let mut client = - connect_command_client_with_retry("http://127.0.0.1:50055".to_owned()).await; + connect_command_client_with_retry(format!("http://127.0.0.1:{port}")).await; let _response_stream = client .connect_command(ReceiverStream::new(grpc_client_receiver)) .await @@ -640,8 +664,8 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb // [itest->swdd~grpc-commander-connection-checks-version-compatibility~1] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] // set worker_threads = 1 to solve the failing of the test on woodpecker async fn itest_grpc_communication_command_connection_refused_on_version_mismatch() { - let (_server_receiver, _to_grpc_server, _grpc_server_task) = - start_insecure_test_grpc_server(50056).await; + let (port, _server_receiver, _to_grpc_server, _grpc_server_task) = + start_test_grpc_server(None).await; let (client_to_server_tx, grpc_client_receiver) = mpsc::channel::(20); @@ -656,7 +680,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb .unwrap(); let mut client = - connect_command_client_with_retry("http://127.0.0.1:50056".to_owned()).await; + connect_command_client_with_retry(format!("http://127.0.0.1:{port}")).await; let result = client .connect_command(ReceiverStream::new(grpc_client_receiver)) .await; @@ -666,8 +690,8 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb #[tokio::test(flavor = "multi_thread", worker_threads = 1)] // set worker_threads = 1 to solve the failing of the test on woodpecker async fn itest_grpc_communication_command_connection_refused_on_missing_commander_hello() { - let (_server_receiver, _to_grpc_server, _grpc_server_task) = - start_insecure_test_grpc_server(50057).await; + let (port, _server_receiver, _to_grpc_server, _grpc_server_task) = + start_test_grpc_server(None).await; let (client_to_server_tx, grpc_client_receiver) = mpsc::channel::(20); @@ -680,7 +704,7 @@ MC4CAQAwBQYDK2VwBCIEILwDB7W+KEw+UkzfOQA9ghy70Em4ubdS42DLkDmdmYyb .unwrap(); let mut client = - connect_command_client_with_retry("http://127.0.0.1:50057".to_owned()).await; + connect_command_client_with_retry(format!("http://127.0.0.1:{port}")).await; let result = client .connect_command(ReceiverStream::new(grpc_client_receiver)) .await;