From 20dc826cbd48b67427c922ed56e4fe57425fd398 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Mon, 2 Feb 2026 22:15:35 +0000 Subject: [PATCH 1/5] Re-org --- .github/workflows/test.yaml | 2 +- README.md | 24 +++++++++++++++---- .../python-web-app}/README.md | 0 .../python-web-app}/deploy.py | 0 .../python-web-app}/docker-start.sh | 0 .../python-web-app}/docker-stop.sh | 0 .../python-web-app}/group_data/all.py | 0 .../group_data/docker-test-server.py | 0 .../python-web-app}/inventories/docker.py | 0 .../python-web-app}/tasks/database.py | 0 .../python-web-app}/tasks/web.py | 0 .../templates/python-app.j2.service | 0 .../deploy-functions}/README.md | 0 .../deploy-functions}/deploy.py | 0 .../inventory-functions}/README.md | 0 .../inventory-functions}/inventory.py | 0 16 files changed, 20 insertions(+), 6 deletions(-) rename {python-web-app => full-deploys/python-web-app}/README.md (100%) rename {python-web-app => full-deploys/python-web-app}/deploy.py (100%) rename {python-web-app => full-deploys/python-web-app}/docker-start.sh (100%) rename {python-web-app => full-deploys/python-web-app}/docker-stop.sh (100%) rename {python-web-app => full-deploys/python-web-app}/group_data/all.py (100%) rename {python-web-app => full-deploys/python-web-app}/group_data/docker-test-server.py (100%) rename {python-web-app => full-deploys/python-web-app}/inventories/docker.py (100%) rename {python-web-app => full-deploys/python-web-app}/tasks/database.py (100%) rename {python-web-app => full-deploys/python-web-app}/tasks/web.py (100%) rename {python-web-app => full-deploys/python-web-app}/templates/python-app.j2.service (100%) rename {deploy-functions => snippets/deploy-functions}/README.md (100%) rename {deploy-functions => snippets/deploy-functions}/deploy.py (100%) rename {inventory-functions => snippets/inventory-functions}/README.md (100%) rename {inventory-functions => snippets/inventory-functions}/inventory.py (100%) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7417b18..8ef88d1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,7 +8,7 @@ jobs: strategy: matrix: include: - - dir: python-web-app + - dir: full-deploys/python-web-app deploy-file: deploy.py inventory-file: inventories/docker.py runs-on: ubuntu-latest diff --git a/README.md b/README.md index 360aad6..6dc87f4 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,32 @@ A set of documented & tested pyinfra deploys. -## Complete Deploys +## Full Deploys -### [`python-web-app`](./python-web-app) +These are complete examples along with Docker test containers (acting as servers accessible via SSH) +and scripts to execute them. These deploys are all tested as part of CI. + +### [`python-web-app`](./full-deploys/python-web-app) Simple: deploys two servers: one database and one web running a Python app fetched using Git. -## Specific Features +### [`foundationdb-cluster`](./full-deploys/foundationdb-cluster) + +Advanced: sets up a five node FoundationDB cluster from scratch. Uses runtime callbacks to bootstrap. + +## Snippets -### [`deploy-functions`](./deploy-functions) +Shorter collections of code snippets that don't always function on their own but demonstrate various +parts of pyinfra functionality. + +### [`deploy-functions`](./snippets/deploy-functions) Shows how to execute Python functions as operations directly from the CLI. -### [`inventory-functions`](./inventory-functions) +### [`inventory-functions`](./snippets/inventory-functions) Look at using Python functions, and external packages, to generate inventory. + +### [`nested-operations`](./snippets/nested-operations) + +Execute operations using output or results of other operations. diff --git a/python-web-app/README.md b/full-deploys/python-web-app/README.md similarity index 100% rename from python-web-app/README.md rename to full-deploys/python-web-app/README.md diff --git a/python-web-app/deploy.py b/full-deploys/python-web-app/deploy.py similarity index 100% rename from python-web-app/deploy.py rename to full-deploys/python-web-app/deploy.py diff --git a/python-web-app/docker-start.sh b/full-deploys/python-web-app/docker-start.sh similarity index 100% rename from python-web-app/docker-start.sh rename to full-deploys/python-web-app/docker-start.sh diff --git a/python-web-app/docker-stop.sh b/full-deploys/python-web-app/docker-stop.sh similarity index 100% rename from python-web-app/docker-stop.sh rename to full-deploys/python-web-app/docker-stop.sh diff --git a/python-web-app/group_data/all.py b/full-deploys/python-web-app/group_data/all.py similarity index 100% rename from python-web-app/group_data/all.py rename to full-deploys/python-web-app/group_data/all.py diff --git a/python-web-app/group_data/docker-test-server.py b/full-deploys/python-web-app/group_data/docker-test-server.py similarity index 100% rename from python-web-app/group_data/docker-test-server.py rename to full-deploys/python-web-app/group_data/docker-test-server.py diff --git a/python-web-app/inventories/docker.py b/full-deploys/python-web-app/inventories/docker.py similarity index 100% rename from python-web-app/inventories/docker.py rename to full-deploys/python-web-app/inventories/docker.py diff --git a/python-web-app/tasks/database.py b/full-deploys/python-web-app/tasks/database.py similarity index 100% rename from python-web-app/tasks/database.py rename to full-deploys/python-web-app/tasks/database.py diff --git a/python-web-app/tasks/web.py b/full-deploys/python-web-app/tasks/web.py similarity index 100% rename from python-web-app/tasks/web.py rename to full-deploys/python-web-app/tasks/web.py diff --git a/python-web-app/templates/python-app.j2.service b/full-deploys/python-web-app/templates/python-app.j2.service similarity index 100% rename from python-web-app/templates/python-app.j2.service rename to full-deploys/python-web-app/templates/python-app.j2.service diff --git a/deploy-functions/README.md b/snippets/deploy-functions/README.md similarity index 100% rename from deploy-functions/README.md rename to snippets/deploy-functions/README.md diff --git a/deploy-functions/deploy.py b/snippets/deploy-functions/deploy.py similarity index 100% rename from deploy-functions/deploy.py rename to snippets/deploy-functions/deploy.py diff --git a/inventory-functions/README.md b/snippets/inventory-functions/README.md similarity index 100% rename from inventory-functions/README.md rename to snippets/inventory-functions/README.md diff --git a/inventory-functions/inventory.py b/snippets/inventory-functions/inventory.py similarity index 100% rename from inventory-functions/inventory.py rename to snippets/inventory-functions/inventory.py From 1f1f7abc3f228f62be1803cff3d453c514536188 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Mon, 2 Feb 2026 22:39:14 +0000 Subject: [PATCH 2/5] Add very basic nested operations example --- snippets/inventory-functions/README.md | 2 +- snippets/nested-operations/README.md | 3 +++ snippets/nested-operations/nested_ops.py | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 snippets/nested-operations/README.md create mode 100644 snippets/nested-operations/nested_ops.py diff --git a/snippets/inventory-functions/README.md b/snippets/inventory-functions/README.md index a0f6619..c4b75ec 100644 --- a/snippets/inventory-functions/README.md +++ b/snippets/inventory-functions/README.md @@ -1,6 +1,6 @@ # Example: Inventory Functions -Like operations inventories can also be defined using Python functions. This brings incredible flexibility allowing you to pull data from external systems using Python's package ecosystem to generate pyinfra inventories. The `inventory.py` contains a triaival example of this which can be executed like so: +Like operations inventories can also be defined using Python functions. This brings incredible flexibility allowing you to pull data from external systems using Python's package ecosystem to generate pyinfra inventories. The `inventory.py` contains an example of this which can be executed like so: ```sh pyinfra inventory.make_docker_inventory exec uptime diff --git a/snippets/nested-operations/README.md b/snippets/nested-operations/README.md new file mode 100644 index 0000000..01c642f --- /dev/null +++ b/snippets/nested-operations/README.md @@ -0,0 +1,3 @@ +# Example: Nested Operations + +Operations within a `python.call` function are called nested operations. These execute immediately, unlike top-level operations which are collected during the planning phase and executed later. The `nested_ops.py` file contains a very simple example of this. diff --git a/snippets/nested-operations/nested_ops.py b/snippets/nested-operations/nested_ops.py new file mode 100644 index 0000000..45119ca --- /dev/null +++ b/snippets/nested-operations/nested_ops.py @@ -0,0 +1,15 @@ +import requests +from pyinfra.operations import apt, python, server + +# Install some service X... +apt.packages(...) + + +def register_device(): + get_idx = server.shell(commands="get identifier from service X") + + # Save the service X identifier in some external system + requests.post("", get_idx.stdout) + + +python.call(function=register_device) From c14d4ddca61b958d60b60f60a3438583a9954f0c Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 3 Feb 2026 09:55:52 +0000 Subject: [PATCH 3/5] Fix paths --- full-deploys/python-web-app/docker-start.sh | 2 +- full-deploys/python-web-app/docker-stop.sh | 2 +- full-deploys/python-web-app/inventories/docker.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/full-deploys/python-web-app/docker-start.sh b/full-deploys/python-web-app/docker-start.sh index 70c30a7..2ed6070 100755 --- a/full-deploys/python-web-app/docker-start.sh +++ b/full-deploys/python-web-app/docker-start.sh @@ -2,7 +2,7 @@ set -euo pipefail -source "$(realpath "$(realpath "$(dirname "${BASH_SOURCE[0]}")")/../utils.sh")" +source "$(realpath "$(realpath "$(dirname "${BASH_SOURCE[0]}")")/../../utils.sh")" ensure_test_container export DOCKER_TEST_NETWORK_NAME="pyinfra-examples-python-web-app" diff --git a/full-deploys/python-web-app/docker-stop.sh b/full-deploys/python-web-app/docker-stop.sh index fa9c663..1fd1498 100755 --- a/full-deploys/python-web-app/docker-stop.sh +++ b/full-deploys/python-web-app/docker-stop.sh @@ -2,7 +2,7 @@ set -euo pipefail -source "$(realpath "$(realpath "$(dirname "${BASH_SOURCE[0]}")")/../utils.sh")" +source "$(realpath "$(realpath "$(dirname "${BASH_SOURCE[0]}")")/../../utils.sh")" export DOCKER_TEST_NETWORK_NAME="pyinfra-examples-python-web-app" diff --git a/full-deploys/python-web-app/inventories/docker.py b/full-deploys/python-web-app/inventories/docker.py index e8fe65b..c73917d 100644 --- a/full-deploys/python-web-app/inventories/docker.py +++ b/full-deploys/python-web-app/inventories/docker.py @@ -10,7 +10,7 @@ # SSH details matching the Docker container started in ./docker-start.sh "ssh_hostname": "localhost", "ssh_user": "pyinfra", - "ssh_key": "../.docker/insecure_private_key", + "ssh_key": "../../.docker/insecure_private_key", "ssh_known_hosts_file": "/dev/null", # This is insecure, don't use in production! "ssh_strict_host_key_checking": "off", From 5ff73b7fe6911c62e15eb8a7c68a75c0ef291f06 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 3 Feb 2026 19:07:59 +0000 Subject: [PATCH 4/5] Use better name for python app deploy file --- full-deploys/python-web-app/{deploy.py => deploy_app.py} | 0 full-deploys/python-web-app/docker-start.sh | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename full-deploys/python-web-app/{deploy.py => deploy_app.py} (100%) diff --git a/full-deploys/python-web-app/deploy.py b/full-deploys/python-web-app/deploy_app.py similarity index 100% rename from full-deploys/python-web-app/deploy.py rename to full-deploys/python-web-app/deploy_app.py diff --git a/full-deploys/python-web-app/docker-start.sh b/full-deploys/python-web-app/docker-start.sh index 2ed6070..b38da7a 100755 --- a/full-deploys/python-web-app/docker-start.sh +++ b/full-deploys/python-web-app/docker-start.sh @@ -17,6 +17,6 @@ run_test_container pyinfra-example-python-web-app-dbserver -p 9023:22 echo echo "Doker containers are now ready to run the pyinfra deploy, you can do this by running:" echo -echo " pyinfra inventories/docker.py deploy.py" +echo " pyinfra inventories/docker.py deploy_app.py" echo echo "Once complete, don't forget to remove the Docker containers and network using the ./docker-stop.sh script!" From f4dea3be76533c03320facebf6d181b1a282eb92 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 3 Feb 2026 20:55:43 +0000 Subject: [PATCH 5/5] Add FoundationDB deploy example --- .github/workflows/test.yaml | 16 +++- .gitignore | 1 + README.md | 2 +- full-deploys/foundationdb-cluster/README.md | 35 +++++++++ .../deploy_foundationdb.py | 40 ++++++++++ .../foundationdb-cluster/docker-start.sh | 25 +++++++ .../foundationdb-cluster/docker-stop.sh | 14 ++++ .../foundationdb-cluster/group_data/all.py | 12 +++ .../inventories/docker.py | 24 ++++++ .../foundationdb-cluster/tasks/bootstrap.py | 46 ++++++++++++ .../foundationdb-cluster/tasks/configure.py | 74 +++++++++++++++++++ .../foundationdb-cluster/tasks/install.py | 38 ++++++++++ .../templates/foundationdb.conf.j2 | 21 ++++++ 13 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 full-deploys/foundationdb-cluster/README.md create mode 100644 full-deploys/foundationdb-cluster/deploy_foundationdb.py create mode 100755 full-deploys/foundationdb-cluster/docker-start.sh create mode 100755 full-deploys/foundationdb-cluster/docker-stop.sh create mode 100644 full-deploys/foundationdb-cluster/group_data/all.py create mode 100644 full-deploys/foundationdb-cluster/inventories/docker.py create mode 100644 full-deploys/foundationdb-cluster/tasks/bootstrap.py create mode 100644 full-deploys/foundationdb-cluster/tasks/configure.py create mode 100644 full-deploys/foundationdb-cluster/tasks/install.py create mode 100644 full-deploys/foundationdb-cluster/templates/foundationdb.conf.j2 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8ef88d1..29a7f47 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,8 +9,13 @@ jobs: matrix: include: - dir: full-deploys/python-web-app - deploy-file: deploy.py + deploy-file: deploy_app.py inventory-file: inventories/docker.py + + - dir: full-deploys/foundationdb-cluster + deploy-file: deploy_foundationdb.py + inventory-file: inventories/docker.py + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -19,10 +24,19 @@ jobs: python-version: "3.12" - uses: docker/setup-buildx-action@v2 - run: pip install pyinfra --pre + - run: ./docker-start.sh working-directory: ${{ matrix.dir }} + - name: Run pyinfra deploy run: pyinfra -y ${{ matrix.inventory-file }} ${{ matrix.deploy-file }} working-directory: ${{ matrix.dir }} + if: ${{ !runner.debug }} + + - name: Run pyinfra deploy (debug mode) + run: pyinfra -y -vvv ${{ matrix.inventory-file }} ${{ matrix.deploy-file }} + working-directory: ${{ matrix.dir }} + if: ${{ runner.debug }} + - run: ./docker-stop.sh working-directory: ${{ matrix.dir }} diff --git a/.gitignore b/.gitignore index 17cb070..3734ef5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .vagrant* +settings.local.json diff --git a/README.md b/README.md index 6dc87f4..2744706 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Simple: deploys two servers: one database and one web running a Python app fetch ### [`foundationdb-cluster`](./full-deploys/foundationdb-cluster) -Advanced: sets up a five node FoundationDB cluster from scratch. Uses runtime callbacks to bootstrap. +Advanced: sets up a five node FoundationDB cluster from scratch. Uses nested operations (callbacks) to bootstrap. ## Snippets diff --git a/full-deploys/foundationdb-cluster/README.md b/full-deploys/foundationdb-cluster/README.md new file mode 100644 index 0000000..aa86f58 --- /dev/null +++ b/full-deploys/foundationdb-cluster/README.md @@ -0,0 +1,35 @@ +# Example: FoundationDB Cluster + +This example deploys a 5-node FoundationDB cluster using pyinfra and Docker containers. Usage: + +```sh +# Start Docker containers +./docker-start.sh + +# Run pyinfra against them +pyinfra inventories/docker.py deploy_foundationdb.py + +# Verify the cluster is health +ssh -p 9022 -i ../../.docker/insecure_private_key pyinfra@localhost +fdbcli --exec "status details" + +# Delete Docker containers +./docker-stop.sh +``` + +## File Layout + +``` +foundationdb-cluster/ +├── deploy_foundationdb.py # Main deployment entry point +├── tasks/ +│ ├── install.py # Package download and installation +│ ├── configure.py # Config files and service management +│ └── bootstrap.py # Cluster initialization +├── templates/ +│ └── foundationdb.conf.j2 # fdbmonitor configuration template +├── group_data/ +│ └── all.py # Shared settings for all hosts +└── inventories/ + └── docker.py # Docker inventory with host data +``` diff --git a/full-deploys/foundationdb-cluster/deploy_foundationdb.py b/full-deploys/foundationdb-cluster/deploy_foundationdb.py new file mode 100644 index 0000000..0ed5e47 --- /dev/null +++ b/full-deploys/foundationdb-cluster/deploy_foundationdb.py @@ -0,0 +1,40 @@ +""" +FoundationDB Cluster Deployment + +Deploys a 5-node FoundationDB cluster with: +- 3 coordinator nodes (nodes 1-3) for quorum +- 5 storage nodes (all nodes) +- Triple redundancy mode + +Usage: + pyinfra inventories/docker.py deploy_foundationdb.py +""" + +from os import path + +from pyinfra import host, local +from pyinfra.operations import apt + +# Update apt cache (with 1 hour cache time to avoid unnecessary updates) +apt.packages( + name="Update apt cache", + packages=["wget"], + cache_time=3600, + update=True, +) + +# Install FoundationDB packages +local.include( + filename=path.join("tasks", "install.py"), +) + +# Configure FoundationDB (config files, cluster file, service) +local.include( + filename=path.join("tasks", "configure.py"), +) + +# Bootstrap cluster (only on the designated bootstrap node) +if host.data.get("is_bootstrap_node"): + local.include( + filename=path.join("tasks", "bootstrap.py"), + ) diff --git a/full-deploys/foundationdb-cluster/docker-start.sh b/full-deploys/foundationdb-cluster/docker-start.sh new file mode 100755 index 0000000..fa89e13 --- /dev/null +++ b/full-deploys/foundationdb-cluster/docker-start.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(realpath "$(realpath "$(dirname "${BASH_SOURCE[0]}")")/../../utils.sh")" +ensure_test_container + +export DOCKER_TEST_NETWORK_NAME="pyinfra-examples-foundationdb-cluster" + +echo "Create Docker network..." +docker network create "$DOCKER_TEST_NETWORK_NAME" + +echo "Starting Docker containers..." +run_test_container pyinfra-example-foundationdb-node-1 -p 9022:22 -p 4500:4500 +run_test_container pyinfra-example-foundationdb-node-2 -p 9023:22 +run_test_container pyinfra-example-foundationdb-node-3 -p 9024:22 +run_test_container pyinfra-example-foundationdb-node-4 -p 9025:22 +run_test_container pyinfra-example-foundationdb-node-5 -p 9026:22 + +echo +echo "Doker containers are now ready to run the pyinfra deploy, you can do this by running:" +echo +echo " pyinfra inventories/docker.py deploy_foundationdb.py" +echo +echo "Once complete, don't forget to remove the Docker containers and network using the ./docker-stop.sh script!" diff --git a/full-deploys/foundationdb-cluster/docker-stop.sh b/full-deploys/foundationdb-cluster/docker-stop.sh new file mode 100755 index 0000000..7f0941a --- /dev/null +++ b/full-deploys/foundationdb-cluster/docker-stop.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(realpath "$(realpath "$(dirname "${BASH_SOURCE[0]}")")/../../utils.sh")" + +export DOCKER_TEST_NETWORK_NAME="pyinfra-examples-foundationdb-cluster" + +docker rm -f pyinfra-example-foundationdb-node-1 +docker rm -f pyinfra-example-foundationdb-node-2 +docker rm -f pyinfra-example-foundationdb-node-3 +docker rm -f pyinfra-example-foundationdb-node-4 +docker rm -f pyinfra-example-foundationdb-node-5 +docker network rm "$DOCKER_TEST_NETWORK_NAME" diff --git a/full-deploys/foundationdb-cluster/group_data/all.py b/full-deploys/foundationdb-cluster/group_data/all.py new file mode 100644 index 0000000..dbdafd3 --- /dev/null +++ b/full-deploys/foundationdb-cluster/group_data/all.py @@ -0,0 +1,12 @@ +fdb_version = "7.3.61" +fdb_port = 4500 +fdb_coordinators = [ + f"pyinfra-example-foundationdb-node-1.pyinfra-examples-foundationdb-cluster:{fdb_port + 1}", + f"pyinfra-example-foundationdb-node-2.pyinfra-examples-foundationdb-cluster:{fdb_port + 1}", + f"pyinfra-example-foundationdb-node-3.pyinfra-examples-foundationdb-cluster:{fdb_port + 1}", +] + +fdb_cluster_name = "docker" +fdb_cluster_id = "pyinfra123456789" # Random cluster identifier +fdb_redundancy_mode = "triple" +fdb_storage_engine = "ssd-redwood-1" diff --git a/full-deploys/foundationdb-cluster/inventories/docker.py b/full-deploys/foundationdb-cluster/inventories/docker.py new file mode 100644 index 0000000..2348e32 --- /dev/null +++ b/full-deploys/foundationdb-cluster/inventories/docker.py @@ -0,0 +1,24 @@ +inventory = ( + # Individual host list with host-specific data + [ + ( + "node-1", + {"ssh_port": 9022, "is_coordinator": True, "is_bootstrap_node": True}, + ), + ("node-2", {"ssh_port": 9023, "is_coordinator": True}), + ("node-3", {"ssh_port": 9024, "is_coordinator": True}), + ("node-4", {"ssh_port": 9025}), + ("node-5", {"ssh_port": 9026}), + ], + # Shared data for all the hosts in the group + { + "_sudo": True, # use sudo for all operations + # SSH details matching the Docker container started in ./docker-start.sh + "ssh_hostname": "localhost", + "ssh_user": "pyinfra", + "ssh_key": "../../.docker/insecure_private_key", + "ssh_known_hosts_file": "/dev/null", + # This is insecure, don't use in production! + "ssh_strict_host_key_checking": "off", + }, +) diff --git a/full-deploys/foundationdb-cluster/tasks/bootstrap.py b/full-deploys/foundationdb-cluster/tasks/bootstrap.py new file mode 100644 index 0000000..bd05ee1 --- /dev/null +++ b/full-deploys/foundationdb-cluster/tasks/bootstrap.py @@ -0,0 +1,46 @@ +""" +FoundationDB cluster bootstrap tasks. +Initializes the cluster configuration (runs only on bootstrap node). +""" + +import json + +from pyinfra import host +from pyinfra.operations import python, server + + +def bootstrap_cluster(): + """ + Initialize the FoundationDB cluster if not already configured. + """ + + # Note: this command will take a while to execute because it has to timeout trying to get the + # status, I have yet to find a quick way to do this check. + check_status = server.shell( + name="Check cluster status", + commands="fdbcli --no-status --exec 'status json' || true", + ) + + status_json = json.loads(check_status.stdout) + status_client = status_json.get("client", {}) + + # Note this is, so far, the best way I can tell to determine if FDB cluster is bootstrapped, + # despite that for production I'd recommend any configure new commands are executed by hand. + if ( + status_client.get("coordinators", {}).get("quorum_reachable") is True + and status_client.get("database_status", {}).get("available") is False + ): + redundancy_mode = host.data.fdb_redundancy_mode + storage_engine = host.data.fdb_storage_engine + server.shell( + name="Configure cluster", + commands=f"fdbcli --no-status --exec 'configure new {redundancy_mode} {storage_engine}'", + ) + + +# Use python.call to execute the bootstrap function at runtime +# This ensures proper state checking during deployment +python.call( + name="Bootstrap FoundationDB cluster", + function=bootstrap_cluster, +) diff --git a/full-deploys/foundationdb-cluster/tasks/configure.py b/full-deploys/foundationdb-cluster/tasks/configure.py new file mode 100644 index 0000000..f30154c --- /dev/null +++ b/full-deploys/foundationdb-cluster/tasks/configure.py @@ -0,0 +1,74 @@ +""" +FoundationDB configuration tasks. +Manages config files, cluster file, and service state. +""" + +from io import StringIO + +from pyinfra import host +from pyinfra.operations import files, systemd + +# Build the cluster file connection string +# Format: description:id@coordinator1,coordinator2,coordinator3 +cluster_description = host.data.fdb_cluster_name +cluster_id = host.data.fdb_cluster_id +coordinators = ",".join(host.data.fdb_coordinators) +cluster_string = f"{cluster_description}:{cluster_id}@{coordinators}" + +# Ensure data directory exists with correct permissions +files.directory( + name="Ensure FDB data directory exists", + path="/var/lib/foundationdb/data", + user="foundationdb", + group="foundationdb", + mode="0755", + present=True, +) + +# Ensure log directory exists with correct permissions +files.directory( + name="Ensure FDB log directory exists", + path="/var/log/foundationdb", + user="foundationdb", + group="foundationdb", + mode="0755", + present=True, +) + +# Deploy foundationdb.conf from template +config_changed = files.template( + name="Deploy foundationdb.conf", + src="templates/foundationdb.conf.j2", + dest="/etc/foundationdb/foundationdb.conf", + user="root", + group="root", + mode="0644", + fdb_port=host.data.fdb_port, + is_coordinator=host.data.get("is_coordinator", False), +) + +# Deploy cluster file +cluster_file_changed = files.put( + name="Deploy fdb.cluster file", + src=StringIO(cluster_string), + dest="/etc/foundationdb/fdb.cluster", + user="foundationdb", + group="foundationdb", + mode="0644", +) + +# Ensure service is enabled and started +systemd.service( + name="Enable and start foundationdb service", + service="foundationdb", + running=True, + enabled=True, +) + +# Restart service if config changed +systemd.service( + name="Restart foundationdb if config changed", + service="foundationdb", + restarted=True, + _if=lambda: config_changed.did_change() or cluster_file_changed.did_change(), +) diff --git a/full-deploys/foundationdb-cluster/tasks/install.py b/full-deploys/foundationdb-cluster/tasks/install.py new file mode 100644 index 0000000..52ba87e --- /dev/null +++ b/full-deploys/foundationdb-cluster/tasks/install.py @@ -0,0 +1,38 @@ +""" +FoundationDB package installation tasks. +Downloads and installs FDB .deb packages from GitHub releases. +""" + +from pyinfra import host +from pyinfra.facts.server import Arch +from pyinfra.operations import apt, files, server + +fdb_version = host.data.fdb_version +fdb_base_url = f"https://github.com/apple/foundationdb/releases/download/{fdb_version}" +arch = host.get_fact(Arch) +if arch == "x86_64": + arch = "amd64" + +# Download FDB packages +files.download( + name="Download foundationdb-clients package", + src=f"{fdb_base_url}/foundationdb-clients_{fdb_version}-1_{arch}.deb", + dest=f"/tmp/foundationdb-clients_{fdb_version}-1_{arch}.deb", +) + +files.download( + name="Download foundationdb-server package", + src=f"{fdb_base_url}/foundationdb-server_{fdb_version}-1_{arch}.deb", + dest=f"/tmp/foundationdb-server_{fdb_version}-1_{arch}.deb", +) + +# Install packages (clients first, then server) +apt.deb( + name="Install foundationdb-clients", + src=f"/tmp/foundationdb-clients_{fdb_version}-1_{arch}.deb", +) + +apt.deb( + name="Install foundationdb-server", + src=f"/tmp/foundationdb-server_{fdb_version}-1_{arch}.deb", +) diff --git a/full-deploys/foundationdb-cluster/templates/foundationdb.conf.j2 b/full-deploys/foundationdb-cluster/templates/foundationdb.conf.j2 new file mode 100644 index 0000000..9517aeb --- /dev/null +++ b/full-deploys/foundationdb-cluster/templates/foundationdb.conf.j2 @@ -0,0 +1,21 @@ +[fdbmonitor] +user = foundationdb +group = foundationdb + +[general] +cluster-file = /etc/foundationdb/fdb.cluster +restart-delay = 60 + +[fdbserver] +command = /usr/sbin/fdbserver +public-address = auto:$ID +listen-address = public +datadir = /var/lib/foundationdb/data/$ID +logdir = /var/log/foundationdb + +[fdbserver.{{ fdb_port }}] +class = storage + +{% if is_coordinator %} +[fdbserver.{{ fdb_port + 1 }}] +{% endif %}