Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions .env

This file was deleted.

5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,3 @@ _notes/

# Auto-generated by hatch-vcs at build time from the latest git tag.
sparkmeter/_version.py

# Local-only env overrides for site-specific compose runs (e.g. pointing
# symds-ground at a real cloud SymmetricDS endpoint). Use with
# `docker compose --env-file .env.local up -d ...`.
.env.local
71 changes: 10 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ uv run flask shell
uv run flask status
```

For Docker deployments, prefix commands with `docker compose exec`:
For Docker deployments, run the same commands inside the webapp container with `docker compose exec`:
```bash
docker compose exec ground uv run flask user create
```
Expand All @@ -100,93 +100,42 @@ This document is to help you get the development environment up and running. You

### Docker

Dockerized development happens in the **groundbolt-dev workspace metarepo**, which clones this repo and its sibling component repos side by side and runs the whole system — the ground and cloud webapps, their databases, the SymmetricDS sync pair, and the `sparknet-http` metering provider — from a single compose file. See that repo's README for setup.

For a fully Dockerized development environment:

```bash
$ docker compose up
```

Pass `-d` to detach. Run with `--watch` to enable Compose's develop watcher, which rebuilds the `ground` and `cloud` images on file changes (note: full image rebuild, not Flask hot-reload — slow for iterative work, useful for verifying built behavior).

#### 1. Build docker services

`docker compose up` builds any missing images automatically, so this step is optional. Run it when you want to force a clean rebuild (e.g. after Dockerfile or dependency changes):

```bash
$ docker compose build --no-cache
```

#### 2. Run the built docker services

The default Compose profile brings up the ground-side stack:

```bash
$ docker compose up -d
```

After it settles, `docker compose ps` shows:

```
ground # the GroundBolt webapp
postgres-ground # its database
metering-provider # the service the ground app calls for meter operations
symds-ground # SymmetricDS node syncing the ground database
```

The ground webapp talks to the metering provider over an OpenAPI HTTP+SSE contract, configured via `METERING_PROVIDER_URL` (default `http://localhost:8000`). The Compose file references the provider image by tag. That image must be present locally; build or pull it under that tag before bringing up the ground stack.

The cloud-side stack lives behind the `cloud` Compose profile. To also bring up the cloud webapp and the cloud-side SymmetricDS node:

```bash
$ docker compose --profile cloud up -d
```

That adds:

```
cloud # the ThunderCloud webapp
postgres-cloud # its database
symds-cloud # SymmetricDS node syncing the cloud database
```

`symds-ground` and `symds-cloud` together provide bidirectional sync between the ground and cloud databases. With only the ground profile active, `symds-ground` runs but has nothing to sync against until `--profile cloud` brings up `symds-cloud`.

#### 3. Voilà! Your development environment is ready!

- The GroundBolt webapp (`ground` container) is at [localhost:8765](http://localhost:8765).
- With `--profile cloud`, the ThunderCloud webapp (`cloud` container) is at [localhost:5010](http://localhost:5010).
This repo's `docker-compose.test.yml` is the self-contained test harness: a throwaway Postgres plus the test-image runner, needing nothing outside this repo. It's what CI runs.

#### Useful commands

##### Run unit tests

```bash
$ docker compose --profile test run --rm test
$ docker compose -f docker-compose.test.yml run --rm test
```

##### Run a subset of unit tests

Override the `test` service's command with a pytest invocation. Any pytest arguments can be passed:

```bash
$ docker compose --profile test run --rm test uv run pytest <path/to/test>[::ClassName][::method_name]
$ docker compose -f docker-compose.test.yml run --rm test uv run pytest <path/to/test>[::ClassName][::method_name]
```

For example, running the tests for the AddCustomer endpoint in the API:

```bash
docker compose --profile test run --rm test uv run pytest sparkmeter/api/tests/test_customerviews0.py::CustomerAddTest
docker compose -f docker-compose.test.yml run --rm test uv run pytest sparkmeter/api/tests/test_customerviews0.py::CustomerAddTest
```

##### Create a new database migration

Database schema migrations are managed via Alembic. To get started, you must first create a migration file.
Database schema migrations are managed via Alembic. To get started, you must first create a migration file:

```bash
docker compose exec ground uv run flask database new-revision "<short description of the migration>"
uv run flask database new-revision "<short description of the migration>"
```

(Or run the same command inside the webapp container of a running Docker stack, via `docker compose exec`.)

This will generate a skeleton migration file in `sparkmeter/alembic/versions/`. From there, customize it your liking.

##### Tail the logs of a service
Expand Down
48 changes: 48 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Test harness, not a dev environment: builds the test image from this repo
# alone and runs the suite against a throwaway Postgres. CI
# (scripts/run_coverage.sh) depends on this working with only this repo
# checked out. Invoke with `docker compose -f docker-compose.test.yml ...`.
#
# The local dev stack (ground/cloud webapps, SymmetricDS pair, metering
# provider) lives in the groundbolt-dev workspace metarepo, which clones this
# repo and its siblings side by side and can therefore build across them.

services:
# Nothing here is named worktree-globally, so two checkouts (a worktree and
# the main checkout, or two worktrees) can run their suites at the same time
# without colliding:
# - No `container_name:`, so re-running the test command after a non-clean
# exit doesn't collide with a leftover container of a fixed name;
# compose auto-names them per-project.
# - No fixed host port. A published `5442:5432` makes the second
# concurrent run die with "Bind for 0.0.0.0:5442 failed: port is
# already allocated". The host doesn't need a fixed port: the test
# container reaches the database over the compose network, and the
# ephemeral publication below still allows poking at it from the host
# via `docker compose -f docker-compose.test.yml port postgres-test 5432`.
postgres-test:
image: library/postgres:18
environment:
- POSTGRES_USER=spark
- POSTGRES_PASSWORD=spark
- POSTGRES_DB=mydb
ports:
# Host port assigned by docker; container port fixed.
- "5432"
healthcheck:
test: ["CMD", "psql", "-U", "spark", "-d", "mydb", "-c", "SELECT 1;"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s

test:
build:
context: '.'
dockerfile: deploy/Dockerfile
target: test
environment:
- DATABASE_URL=postgresql://spark:spark@postgres-test/mydb
depends_on:
- postgres-test
command: uv run pytest -n auto
Loading
Loading