diff --git a/.github/workflows/scenario-integration-tests.yml b/.github/workflows/scenario-integration-tests.yml index 76545be8c1..7da0fa8246 100644 --- a/.github/workflows/scenario-integration-tests.yml +++ b/.github/workflows/scenario-integration-tests.yml @@ -26,7 +26,7 @@ jobs: test: runs-on: ubuntu-latest # Run on openwallet-foundation and non-draft PRs or on non-PR events - if: (github.repository == 'openwallet-foundation/acapy') && ((github.event_name == 'pull_request' && github.event.pull_request.draft == false) || (github.event_name != 'pull_request')) + if: (github.repository == 'jamshale/acapy') && ((github.event_name == 'pull_request' && github.event.pull_request.draft == false) || (github.event_name != 'pull_request')) steps: - name: checkout-acapy uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -61,8 +61,25 @@ jobs: - name: Run Scenario Tests if: steps.check-if-scenarios-or-src-changed.outputs.run_tests != 'false' run: | - # Build the docker image for testing + set -euo pipefail + docker build -t acapy-test -f docker/Dockerfile.run . + cd scenarios - poetry install --no-root - poetry run pytest -m examples + + for examples_file in $(find examples -type f -name 'example.py'); do + examples_dir=$(dirname "$examples_file") + echo -e "\nšŸ”Ž Running scenario in: $examples_dir" + cd "$examples_dir" + docker compose build + docker compose up -d + if ! docker compose run --rm example; then + echo "āŒ Tests failed for $examples_dir - dumping logs:" + docker compose logs + docker compose down --remove-orphans + exit 1 + fi + echo "āœ… Tests passed for $examples_dir" + docker compose down --remove-orphans + cd - > /dev/null + done diff --git a/acapy_agent/anoncreds/revocation.py b/acapy_agent/anoncreds/revocation.py index 4a6f9f0d5c..f5f4aff282 100644 --- a/acapy_agent/anoncreds/revocation.py +++ b/acapy_agent/anoncreds/revocation.py @@ -993,46 +993,48 @@ def _has_required_id_and_tails_path(): rev_list = None if _has_required_id_and_tails_path(): - async with self.profile.session() as session: - rev_reg_def = await session.handle.fetch( - CATEGORY_REV_REG_DEF, rev_reg_def_id - ) - rev_list = await session.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id) - rev_key = await session.handle.fetch( + # We need to make sure the read, index increment, and write + # operations on the revocation list are atomic. + # This is done by using a transaction. + async with self.profile.transaction() as txn: + rev_reg_def = await txn.handle.fetch(CATEGORY_REV_REG_DEF, rev_reg_def_id) + rev_list = await txn.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id) + rev_key = await txn.handle.fetch( CATEGORY_REV_REG_DEF_PRIVATE, rev_reg_def_id ) - _handle_missing_entries(rev_list, rev_reg_def, rev_key) + _handle_missing_entries(rev_list, rev_reg_def, rev_key) - rev_list_value_json = rev_list.value_json - rev_list_tags = rev_list.tags + rev_list_value_json = rev_list.value_json + rev_list_tags = rev_list.tags - # If the rev_list state is failed then the tails file was never uploaded, - # try to upload it now and finish the revocation list - if rev_list_tags.get("state") == RevListState.STATE_FAILED: - await self.upload_tails_file( - RevRegDef.deserialize(rev_reg_def.value_json) - ) - rev_list_tags["state"] = RevListState.STATE_FINISHED - - rev_reg_index = rev_list_value_json["next_index"] - try: - rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) - rev_list = RevocationStatusList.load(rev_list_value_json["rev_list"]) - except AnoncredsError as err: - raise AnonCredsRevocationError( - "Error loading revocation registry" - ) from err + # If the rev_list state is failed then the tails file was never uploaded, + # try to upload it now and finish the revocation list + if rev_list_tags.get("state") == RevListState.STATE_FAILED: + await self.upload_tails_file( + RevRegDef.deserialize(rev_reg_def.value_json) + ) + rev_list_tags["state"] = RevListState.STATE_FINISHED - # NOTE: we increment the index ahead of time to keep the - # transaction short. The revocation registry itself will NOT - # be updated because we always use ISSUANCE_BY_DEFAULT. - # If something goes wrong later, the index will be skipped. - # FIXME - double check issuance type in case of upgraded wallet? - if rev_reg_index > rev_reg_def.max_cred_num: - raise AnonCredsRevocationRegistryFullError("Revocation registry is full") - rev_list_value_json["next_index"] = rev_reg_index + 1 - async with self.profile.transaction() as txn: + rev_reg_index = rev_list_value_json["next_index"] + try: + rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) + rev_list = RevocationStatusList.load(rev_list_value_json["rev_list"]) + except AnoncredsError as err: + raise AnonCredsRevocationError( + "Error loading revocation registry" + ) from err + + # NOTE: we increment the index ahead of time to keep the + # transaction short. The revocation registry itself will NOT + # be updated because we always use ISSUANCE_BY_DEFAULT. + # If something goes wrong later, the index will be skipped. + # FIXME - double check issuance type in case of upgraded wallet? + if rev_reg_index > rev_reg_def.max_cred_num: + raise AnonCredsRevocationRegistryFullError( + "Revocation registry is full" + ) + rev_list_value_json["next_index"] = rev_reg_index + 1 await txn.handle.replace( CATEGORY_REV_LIST, rev_reg_def_id, diff --git a/scenarios/examples/anoncreds_issuance_and_revocation/example.py b/scenarios/examples/anoncreds_issuance_and_revocation/example.py index a9f7c6b4e7..af004dc05e 100644 --- a/scenarios/examples/anoncreds_issuance_and_revocation/example.py +++ b/scenarios/examples/anoncreds_issuance_and_revocation/example.py @@ -4,6 +4,7 @@ """ import asyncio +import sys from datetime import datetime from os import getenv from secrets import token_hex @@ -555,4 +556,8 @@ async def main(): if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/clustered/docker-compose.yml b/scenarios/examples/clustered/docker-compose.yml new file mode 100644 index 0000000000..4885df674c --- /dev/null +++ b/scenarios/examples/clustered/docker-compose.yml @@ -0,0 +1,144 @@ + + services: + nginx: + image: nginx:latest + container_name: test_nginx + ports: + - "8080:80" # Host:Container for Admin API + - "5000:5000" # Host:Container for didcomm + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - faber + - alice + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 15s + + faber: + image: acapy-test + ports: + - "4000" + deploy: + replicas: 3 + restart: unless-stopped # Due to askar store race condition. Can remove if that's fixed. + command: > + start + --label Faber + --inbound-transport http 0.0.0.0 3000 + --outbound-transport http + --endpoint http://nginx:5000 + --admin 0.0.0.0 4000 + --admin-insecure-mode + --tails-server-base-url http://tails:6543 + --genesis-url http://test.bcovrin.vonx.io/genesis + --wallet-type askar-anoncreds + --wallet-name faber + --wallet-key insecure + --wallet-storage-type "postgres_storage" + --wallet-storage-config "{\"url\":\"wallet-db:5432\",\"max_connections\":5}" + --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}" + --auto-accept-invites + --auto-accept-requests + --auto-provision + --log-level info + --debug-webhooks + --notify-revocation + healthcheck: + test: curl -s -o /dev/null -w '%{http_code}' "http://localhost:4000/status/live" | grep "200" > /dev/null + start_period: 30s + interval: 7s + timeout: 5s + retries: 5 + depends_on: + tails: + condition: service_started + wallet-db: + condition: service_healthy + + alice: + image: acapy-test + ports: + - "6001:6001" + command: > + start + --label Alice + --inbound-transport http 0.0.0.0 6000 + --outbound-transport http + --endpoint http://alice:6000 + --admin 0.0.0.0 6001 + --admin-insecure-mode + --tails-server-base-url http://tails:6543 + --genesis-url http://test.bcovrin.vonx.io/genesis + --wallet-type askar + --wallet-name alice + --wallet-key insecure + --auto-provision + --auto-accept-invites + --auto-accept-requests + --log-level info + --debug-webhooks + --monitor-revocation-notification + healthcheck: + test: curl -s -o /dev/null -w '%{http_code}' "http://alice:6001/status/live" | grep "200" > /dev/null + start_period: 30s + interval: 7s + timeout: 5s + retries: 5 + depends_on: + tails: + condition: service_started + wallet-db: + condition: service_healthy + + example: + container_name: controller + build: + context: ../.. + environment: + - FABER=http://nginx + - ALICE=http://alice:6001 + volumes: + - ./example.py:/usr/src/app/example.py:ro,z + command: python -m example + depends_on: + faber: + condition: service_healthy + alice: + condition: service_healthy + wallet-db: + condition: service_healthy + nginx: + condition: service_started + + tails: + image: ghcr.io/bcgov/tails-server:latest + ports: + - 6543:6543 + environment: + - GENESIS_URL=http://test.bcovrin.vonx.io/genesis + command: > + tails-server + --host 0.0.0.0 + --port 6543 + --storage-path /tmp/tails-files + --log-level info + + wallet-db: + image: postgres:12 + environment: + - POSTGRES_USER=DB_USER + - POSTGRES_PASSWORD=DB_PASSWORD + ports: + - 5433:5432 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U DB_USER"] + interval: 10s + retries: 5 + start_period: 30s + timeout: 10s + + \ No newline at end of file diff --git a/scenarios/examples/clustered/example.py b/scenarios/examples/clustered/example.py new file mode 100644 index 0000000000..943c20ba07 --- /dev/null +++ b/scenarios/examples/clustered/example.py @@ -0,0 +1,239 @@ +"""Example of issuing multiple credentials with anoncreds in a clustered environment.""" + +import asyncio +import sys +from os import getenv +from secrets import token_hex + +import aiohttp +from acapy_controller import Controller +from acapy_controller.logging import logging_to_stdout +from acapy_controller.protocols import ( + DIDResult, + InvitationRecord, + OobRecord, + params, +) +from aiohttp import ClientSession +from examples.util import ( + CredDefResultAnonCreds, + SchemaResultAnonCreds, + V20CredExRecord, +) + +CREDENTIALS_BASE_PATH = "/issue-credential-2.0" +REVOCATION_BASE_PATH = "/anoncreds" + +FABER = getenv("FABER", "http://nginx") +ALICE = getenv("ALICE", "http://alice:6001") + + +async def check_unique_cred_rev_ids( + agent: Controller, credential_exchange_ids: list[str] +) -> None: + """Check that all credential revocation IDs are unique.""" + seen = [] + + for cred_ex_id in credential_exchange_ids: + result = ( + await agent.get( + f"{REVOCATION_BASE_PATH}/revocation/credential-record?cred_ex_id={cred_ex_id}" + ) + )["result"] + + cred_rev_id = int(result["cred_rev_id"]) + if cred_rev_id not in seen: + seen.append(cred_rev_id) + else: + raise AssertionError( + f"Duplicate cred_rev_id found: {cred_rev_id} for credential {cred_ex_id}" + ) + + print(f"Unique cred_rev_ids found: {len(seen)}") + seen.sort() + print(f"Credential revocation IDs: {seen}") + + +async def main(): + """Test Controller protocols.""" + async with Controller(base_url=ALICE) as alice, Controller(base_url=FABER) as faber: + # Connecting + + invite_record = await faber.post( + "/out-of-band/create-invitation", + json={ + "handshake_protocols": ["https://didcomm.org/didexchange/1.1"], + }, + params=params( + auto_accept=True, + ), + response=InvitationRecord, + ) + + await alice.post( + "/out-of-band/receive-invitation", + json=invite_record.invitation, + response=OobRecord, + ) + + await asyncio.sleep(1) # Wait for the invitation to be processed + + alice_conn = (await faber.get("connections"))["results"][0] + + # Issuance prep + config = (await alice.get("/status/config"))["config"] + genesis_url = config.get("ledger.genesis_url") + public_did = (await alice.get("/wallet/did/public", response=DIDResult)).result + if not public_did: + public_did = ( + await faber.post( + "/wallet/did/create", + json={"method": "sov", "options": {"key_type": "ed25519"}}, + response=DIDResult, + ) + ).result + assert public_did + + async with ClientSession() as session: + register_url = genesis_url.replace("/genesis", "/register") + async with session.post( + register_url, + json={ + "did": public_did.did, + "verkey": public_did.verkey, + "alias": None, + "role": "ENDORSER", + }, + ) as resp: + assert resp.ok + + await faber.post("/wallet/did/public", params=params(did=public_did.did)) + schema_name = "anoncreds-test-" + token_hex(8) + schema_version = "1.0" + schema = await faber.post( + "/anoncreds/schema", + json={ + "schema": { + "name": schema_name, + "version": schema_version, + "attrNames": ["middlename"], + "issuerId": public_did.did, + } + }, + response=SchemaResultAnonCreds, + ) + cred_def = await faber.post( + "/anoncreds/credential-definition", + json={ + "credential_definition": { + "issuerId": schema.schema_state["schema"]["issuerId"], + "schemaId": schema.schema_state["schema_id"], + "tag": token_hex(8), + }, + "options": {"support_revocation": True, "revocation_registry_size": 100}, + }, + response=CredDefResultAnonCreds, + ) + + num_creds = 10 # Number of credentials to issue concurrently + + # Create and send credential offers concurrently + faber_cred_ex_ids = [] + for i in range(num_creds): + issuer_cred_ex = await faber.post( + "/issue-credential-2.0/send-offer", + json={ + "auto_issue": True, + "auto_remove": False, + "comment": "Credential from minimal example", + "trace": False, + "connection_id": alice_conn["connection_id"], + "filter": { + "anoncreds": { + "cred_def_id": cred_def.credential_definition_state[ + "credential_definition_id" + ], + "schema_name": schema_name, + "schema_version": schema_version, + } + }, + "credential_preview": { + "type": "issue-credential-2.0/2.0/credential-preview", # pyright: ignore + "attributes": [ + { + "name": "middlename", + "value": f"MiddleName-{i + 1}", + } + ], + }, + }, + response=V20CredExRecord, + ) + faber_cred_ex_ids.append(issuer_cred_ex.cred_ex_id) + + # Wait for all credentials to be received by Alice + num_tries = 0 + credentials_returned = {"results": []} + while ( + len(credentials_returned["results"]) != num_creds and num_tries < 20 + ): # Increased timeout for many creds + await asyncio.sleep(0.5) + credentials_returned = await alice.get(f"{CREDENTIALS_BASE_PATH}/records") + num_tries += 1 + + print(f"Number of credentials returned: {len(credentials_returned['results'])}") + + assert len(credentials_returned["results"]) == num_creds, ( + f"Expected {num_creds} credentials to be issued; only got {credentials_returned}" + ) + + # Accept all credentials concurrently using asyncio.gather() + request_tasks = [] + for cred in credentials_returned["results"]: + task = alice.post( + f"{CREDENTIALS_BASE_PATH}/records/{cred['cred_ex_record']['cred_ex_id']}/send-request", + json={}, + ) + request_tasks.append(task) + + # Execute all credential requests concurrently + await asyncio.gather(*request_tasks) + + # Wait for all credentials to be completed. + # This could be done more efficiently with a webhook listener, but + # is challenging with the current Controller library and multiple instances. + await asyncio.sleep(3) + async with aiohttp.ClientSession() as session: + seen = [] + + active_rev_reg = ( + await ( + await session.get( + f"http://nginx/anoncreds/revocation/active-registry/{cred_def.credential_definition_state['credential_definition_id']}" + ) + ).json() + )["result"]["revoc_reg_id"] + + results = await session.get( + f"http://nginx/anoncreds/revocation/registry/{active_rev_reg}/issued/details" + ) + + for entry in await results.json(): + if entry["cred_rev_id"] not in seen: + seen.append(entry["cred_rev_id"]) + else: + raise AssertionError( + f"Duplicate cred_rev_id found: {entry['cred_rev_id']}" + ) + + print(f"Credential revocation IDs created: {seen}") + sys.exit(0) + + +if __name__ == "__main__": + logging_to_stdout() + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/clustered/nginx.conf b/scenarios/examples/clustered/nginx.conf new file mode 100644 index 0000000000..70b1210deb --- /dev/null +++ b/scenarios/examples/clustered/nginx.conf @@ -0,0 +1,55 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + upstream backend_cluster { + server faber:4000; + # You can add weights or max_fails if needed + # server 127.0.0.1:8001 weight=2; + } + + server { + listen 80; + + location / { + proxy_pass http://backend_cluster; + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # Forward real IP and host headers + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } + + upstream didcomm_cluster { + server faber:3000; + # You can add weights or max_fails if needed + # server 127.0.0.1:8001 weight=2; + } + + server { + listen 5000; + + location / { + proxy_pass http://didcomm_cluster; + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # Forward real IP and host headers + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} \ No newline at end of file diff --git a/scenarios/examples/connectionless/docker-compose.yml b/scenarios/examples/connectionless/docker-compose.yml index 7bc9e0852a..bcf023edd2 100644 --- a/scenarios/examples/connectionless/docker-compose.yml +++ b/scenarios/examples/connectionless/docker-compose.yml @@ -19,7 +19,7 @@ --wallet-name alice --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks healthcheck: test: curl -s -o /dev/null -w '%{http_code}' "http://localhost:3001/status/live" | grep "200" > /dev/null @@ -51,7 +51,7 @@ --wallet-name bob --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification healthcheck: diff --git a/scenarios/examples/connectionless/example.py b/scenarios/examples/connectionless/example.py index 7c0d867461..ea6dfac3c7 100644 --- a/scenarios/examples/connectionless/example.py +++ b/scenarios/examples/connectionless/example.py @@ -4,6 +4,7 @@ """ import asyncio +import sys from dataclasses import dataclass from os import getenv @@ -284,6 +285,7 @@ async def icv1(): credential_exchange_id=bob_cred_ex_id, state="credential_acked", ) + sys.exit(0) async def main(): @@ -294,4 +296,8 @@ async def main(): if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/did_indy_issuance_and_revocation/example.py b/scenarios/examples/did_indy_issuance_and_revocation/example.py index 407de5e2a5..55c2004f20 100644 --- a/scenarios/examples/did_indy_issuance_and_revocation/example.py +++ b/scenarios/examples/did_indy_issuance_and_revocation/example.py @@ -5,6 +5,7 @@ import asyncio import json +import sys from dataclasses import dataclass from os import getenv @@ -115,8 +116,13 @@ async def main(): ) await indy_anoncreds_publish_revocation(alice, cred_ex=alice_cred_ex) await bob.record(topic="revocation-notification") + sys.exit(0) if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/json_ld/docker-compose.yml b/scenarios/examples/json_ld/docker-compose.yml index b4a14e6b73..accaa190d4 100644 --- a/scenarios/examples/json_ld/docker-compose.yml +++ b/scenarios/examples/json_ld/docker-compose.yml @@ -9,7 +9,7 @@ -ot http -e http://alice:3000 --admin 0.0.0.0 3001 --admin-insecure-mode - --log-level debug + --log-level info --genesis-url http://test.bcovrin.vonx.io/genesis --wallet-type askar --wallet-name alice @@ -34,7 +34,7 @@ -ot http -e http://bob:3000 --admin 0.0.0.0 3001 --admin-insecure-mode - --log-level debug + --log-level info --genesis-url http://test.bcovrin.vonx.io/genesis --wallet-type askar --wallet-name bob diff --git a/scenarios/examples/json_ld/example.py b/scenarios/examples/json_ld/example.py index 1629335fc0..89172e0604 100644 --- a/scenarios/examples/json_ld/example.py +++ b/scenarios/examples/json_ld/example.py @@ -5,6 +5,7 @@ import asyncio import json +import sys from datetime import date from os import getenv from uuid import uuid4 @@ -449,7 +450,13 @@ async def main(): with section("Presentation summary", character="-"): print(presentation_summary(alice_pres_ex.into(V20PresExRecord))) + sys.exit(0) + if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/mediation/docker-compose.yml b/scenarios/examples/mediation/docker-compose.yml index b6e9dbebbb..c5e608523e 100644 --- a/scenarios/examples/mediation/docker-compose.yml +++ b/scenarios/examples/mediation/docker-compose.yml @@ -18,7 +18,7 @@ --wallet-name alice --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks healthcheck: test: curl -s -o /dev/null -w '%{http_code}' "http://localhost:3001/status/live" | grep "200" > /dev/null @@ -46,7 +46,7 @@ --wallet-name bob --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification healthcheck: @@ -75,7 +75,7 @@ --wallet-name mediator --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --enable-undelivered-queue healthcheck: diff --git a/scenarios/examples/mediation/example.py b/scenarios/examples/mediation/example.py index 3de69ad703..c005940e05 100644 --- a/scenarios/examples/mediation/example.py +++ b/scenarios/examples/mediation/example.py @@ -4,6 +4,7 @@ """ import asyncio +import sys from os import getenv from acapy_controller import Controller @@ -33,7 +34,13 @@ async def main(): ab, ba = await didexchange(alice, bob) await trustping(alice, ab) + sys.exit(0) + if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/multitenancy/docker-compose.yml b/scenarios/examples/multitenancy/docker-compose.yml index 040771c60a..494ede8573 100644 --- a/scenarios/examples/multitenancy/docker-compose.yml +++ b/scenarios/examples/multitenancy/docker-compose.yml @@ -21,7 +21,7 @@ --multitenant-admin --jwt-secret insecure --multitenancy-config wallet_type=single-wallet-askar key_derivation_method=RAW - --log-level debug + --log-level info --debug-webhooks healthcheck: test: curl -s -o /dev/null -w '%{http_code}' "http://localhost:3001/status/live" | grep "200" > /dev/null diff --git a/scenarios/examples/multitenancy/example.py b/scenarios/examples/multitenancy/example.py index 2d8b2529c9..41cc21c07f 100644 --- a/scenarios/examples/multitenancy/example.py +++ b/scenarios/examples/multitenancy/example.py @@ -4,6 +4,7 @@ """ import asyncio +import sys from os import getenv from acapy_controller import Controller @@ -105,8 +106,13 @@ async def main(): alice_conn.connection_id, requested_attributes=[{"name": "firstname"}], ) + sys.exit(0) if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/multiuse_invitations/docker-compose.yml b/scenarios/examples/multiuse_invitations/docker-compose.yml index 7bc9e0852a..bcf023edd2 100644 --- a/scenarios/examples/multiuse_invitations/docker-compose.yml +++ b/scenarios/examples/multiuse_invitations/docker-compose.yml @@ -19,7 +19,7 @@ --wallet-name alice --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks healthcheck: test: curl -s -o /dev/null -w '%{http_code}' "http://localhost:3001/status/live" | grep "200" > /dev/null @@ -51,7 +51,7 @@ --wallet-name bob --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification healthcheck: diff --git a/scenarios/examples/multiuse_invitations/example.py b/scenarios/examples/multiuse_invitations/example.py index 40a25536ae..a3fe413669 100644 --- a/scenarios/examples/multiuse_invitations/example.py +++ b/scenarios/examples/multiuse_invitations/example.py @@ -4,6 +4,7 @@ """ import asyncio +import sys from os import getenv from acapy_controller import Controller @@ -27,8 +28,13 @@ async def main(): a2 = a2.serialize() assert a2["invitation_msg_id"] assert a1["invitation_msg_id"] == a2["invitation_msg_id"] + sys.exit(0) if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/presenting_revoked_credential/docker-compose.yml b/scenarios/examples/presenting_revoked_credential/docker-compose.yml index 221ec26300..67e4424893 100644 --- a/scenarios/examples/presenting_revoked_credential/docker-compose.yml +++ b/scenarios/examples/presenting_revoked_credential/docker-compose.yml @@ -17,7 +17,7 @@ --wallet-name alice --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --notify-revocation healthcheck: @@ -48,7 +48,7 @@ --wallet-name bob --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification healthcheck: diff --git a/scenarios/examples/presenting_revoked_credential/example.py b/scenarios/examples/presenting_revoked_credential/example.py index 2e3cdd80b4..240edfe3e1 100644 --- a/scenarios/examples/presenting_revoked_credential/example.py +++ b/scenarios/examples/presenting_revoked_credential/example.py @@ -5,6 +5,7 @@ import asyncio import json +import sys import time from os import getenv @@ -191,8 +192,13 @@ async def main(): # Presentation summary for i, pres in enumerate(presentations.results): print(summary(pres)) + sys.exit(0) if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/restart_anoncreds_upgrade/docker-compose.yml b/scenarios/examples/restart_anoncreds_upgrade/docker-compose.yml index caddadc8be..6b15329d8d 100644 --- a/scenarios/examples/restart_anoncreds_upgrade/docker-compose.yml +++ b/scenarios/examples/restart_anoncreds_upgrade/docker-compose.yml @@ -36,7 +36,7 @@ services: --wallet-storage-config "{\"url\":\"wallet-db:5432\",\"max_connections\":5}" --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}" --auto-provision - --log-level debug + --log-level info --debug-webhooks --notify-revocation --preserve-exchange-records @@ -75,7 +75,7 @@ services: --wallet-storage-config "{\"url\":\"wallet-db:5432\",\"max_connections\":5}" --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}" --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification --preserve-exchange-records @@ -114,7 +114,7 @@ services: --wallet-storage-config "{\"url\":\"wallet-db:5432\",\"max_connections\":5}" --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}" --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification --preserve-exchange-records @@ -153,7 +153,7 @@ services: --wallet-storage-config "{\"url\":\"wallet-db:5432\",\"max_connections\":5}" --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}" --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification --preserve-exchange-records diff --git a/scenarios/examples/restart_anoncreds_upgrade/example.py b/scenarios/examples/restart_anoncreds_upgrade/example.py index cc272c1d11..98a7dc506f 100644 --- a/scenarios/examples/restart_anoncreds_upgrade/example.py +++ b/scenarios/examples/restart_anoncreds_upgrade/example.py @@ -5,6 +5,7 @@ import asyncio import json +import sys from os import getenv from acapy_controller import Controller @@ -493,8 +494,13 @@ async def main(): if bob_id and new_bob_container: # cleanup - shut down bob agent (not part of docker compose) stop_and_remove_container(client, bob_id) + sys.exit(0) if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/self_attested/docker-compose.yml b/scenarios/examples/self_attested/docker-compose.yml index b66e9268e8..6c63a3be2b 100644 --- a/scenarios/examples/self_attested/docker-compose.yml +++ b/scenarios/examples/self_attested/docker-compose.yml @@ -17,7 +17,7 @@ --wallet-name alice --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks healthcheck: test: curl -s -o /dev/null -w '%{http_code}' "http://localhost:3001/status/live" | grep "200" > /dev/null @@ -47,7 +47,7 @@ --wallet-name bob --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification healthcheck: diff --git a/scenarios/examples/simple/docker-compose.yml b/scenarios/examples/simple/docker-compose.yml index 199f3d8957..aa62289d26 100644 --- a/scenarios/examples/simple/docker-compose.yml +++ b/scenarios/examples/simple/docker-compose.yml @@ -19,7 +19,7 @@ --wallet-name alice --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --universal-resolver healthcheck: @@ -52,7 +52,7 @@ --wallet-name bob --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification healthcheck: diff --git a/scenarios/examples/simple/example.py b/scenarios/examples/simple/example.py index a41994e569..226db8468c 100644 --- a/scenarios/examples/simple/example.py +++ b/scenarios/examples/simple/example.py @@ -4,6 +4,7 @@ """ import asyncio +import sys from os import getenv from acapy_controller import Controller @@ -18,8 +19,13 @@ async def main(): """Test Controller protocols.""" async with Controller(base_url=ALICE) as alice, Controller(base_url=BOB) as bob: await didexchange(alice, bob) + sys.exit(0) if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/simple_restart/docker-compose.yml b/scenarios/examples/simple_restart/docker-compose.yml index 59a7ed2d28..14555fbb72 100644 --- a/scenarios/examples/simple_restart/docker-compose.yml +++ b/scenarios/examples/simple_restart/docker-compose.yml @@ -36,7 +36,7 @@ services: --wallet-storage-config "{\"url\":\"wallet-db:5432\",\"max_connections\":5}" --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}" --auto-provision - --log-level debug + --log-level info --debug-webhooks --preserve-exchange-records healthcheck: @@ -74,7 +74,7 @@ services: --wallet-storage-config "{\"url\":\"wallet-db:5432\",\"max_connections\":5}" --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}" --auto-provision - --log-level debug + --log-level info --debug-webhooks --monitor-revocation-notification --preserve-exchange-records diff --git a/scenarios/examples/simple_restart/example.py b/scenarios/examples/simple_restart/example.py index 3727568da9..aee18e6df5 100644 --- a/scenarios/examples/simple_restart/example.py +++ b/scenarios/examples/simple_restart/example.py @@ -4,6 +4,7 @@ """ import asyncio +import sys from os import getenv from acapy_controller import Controller @@ -248,8 +249,13 @@ async def main(): agency_container.stop() wait_until_healthy(client, agency_id, is_healthy=False) agency_container.remove() + sys.exit(0) if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/scenarios/examples/vc_holder/docker-compose.yml b/scenarios/examples/vc_holder/docker-compose.yml index ac9d0ef7a4..f0fd81e542 100644 --- a/scenarios/examples/vc_holder/docker-compose.yml +++ b/scenarios/examples/vc_holder/docker-compose.yml @@ -18,7 +18,7 @@ --wallet-name alice --wallet-key insecure --auto-provision - --log-level debug + --log-level info --debug-webhooks --multitenant --multitenant-admin diff --git a/scenarios/examples/vc_holder/example.py b/scenarios/examples/vc_holder/example.py index 1e214419f0..8c0a3c4e0b 100644 --- a/scenarios/examples/vc_holder/example.py +++ b/scenarios/examples/vc_holder/example.py @@ -1,6 +1,7 @@ """Test VC Holder multi-tenancy isolation.""" import asyncio +import sys from os import getenv from acapy_controller import Controller @@ -88,8 +89,13 @@ async def main(): ) result = await bob.get("/vc/credentials") assert len(result["results"]) == 0 + sys.exit(0) if __name__ == "__main__": logging_to_stdout() - asyncio.run(main()) + try: + asyncio.run(main()) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1)