From 83a26f684e26c16afdca9f4a1095ce0e25491619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Str=C3=BCbe?= Date: Wed, 7 Oct 2026 13:52:00 +0200 Subject: [PATCH] test(integration): use pre-provisioned no-user service account (FB-4691) test_account_no_user created a service account at runtime, which requires the CI credential to be an organization admin. Read the credentials of the static ecosystem_ci_sa_no_user fixture from SERVICE_ID_NO_USER / SERVICE_SECRET_NO_USER instead, so the CI credential can be reduced to system_admin on a single account. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/integration-tests-v2.yml | 6 +++ .github/workflows/integration-tests.yml | 6 +++ .github/workflows/nightly-v2.yml | 2 + .github/workflows/release.yml | 2 + tests/integration/conftest.py | 11 ++++++ tests/integration/dbapi/async/V2/conftest.py | 40 -------------------- tests/integration/dbapi/sync/V2/conftest.py | 40 -------------------- 7 files changed, 27 insertions(+), 80 deletions(-) diff --git a/.github/workflows/integration-tests-v2.yml b/.github/workflows/integration-tests-v2.yml index c29fad76b26..28cec459a50 100644 --- a/.github/workflows/integration-tests-v2.yml +++ b/.github/workflows/integration-tests-v2.yml @@ -7,6 +7,10 @@ on: required: true FIREBOLT_CLIENT_SECRET_STG_NEW_IDN: required: true + ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG: + required: true + ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG: + required: true inputs: engine-version: description: 'Engine version to use for integration tests' @@ -49,6 +53,8 @@ jobs: env: SERVICE_ID: ${{ secrets.FIREBOLT_CLIENT_ID_STG_NEW_IDN }} SERVICE_SECRET: ${{ secrets.FIREBOLT_CLIENT_SECRET_STG_NEW_IDN }} + SERVICE_ID_NO_USER: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG }} + SERVICE_SECRET_NO_USER: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG }} DATABASE_NAME: ${{ steps.setup.outputs.database_name }} ENGINE_NAME: ${{ steps.setup.outputs.engine_name }} STOPPED_ENGINE_NAME: ${{ steps.setup.outputs.stopped_engine_name }} diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index eb9760da9c3..ddeee4fc7aa 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -7,12 +7,18 @@ on: required: true FIREBOLT_CLIENT_SECRET_STG_NEW_IDN: required: true + ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG: + required: true + ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG: + required: true jobs: integration-test-v2: uses: ./.github/workflows/integration-tests-v2.yml secrets: FIREBOLT_CLIENT_ID_STG_NEW_IDN: ${{ secrets.FIREBOLT_CLIENT_ID_STG_NEW_IDN }} FIREBOLT_CLIENT_SECRET_STG_NEW_IDN: ${{ secrets.FIREBOLT_CLIENT_SECRET_STG_NEW_IDN }} + ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG }} + ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG }} integration-test-core: uses: ./.github/workflows/integration-tests-core.yml report-test-results: diff --git a/.github/workflows/nightly-v2.yml b/.github/workflows/nightly-v2.yml index c290b690a6f..2f39ab93424 100644 --- a/.github/workflows/nightly-v2.yml +++ b/.github/workflows/nightly-v2.yml @@ -69,6 +69,8 @@ jobs: env: SERVICE_ID: ${{ secrets.FIREBOLT_CLIENT_ID_STG_NEW_IDN }} SERVICE_SECRET: ${{ secrets.FIREBOLT_CLIENT_SECRET_STG_NEW_IDN }} + SERVICE_ID_NO_USER: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG }} + SERVICE_SECRET_NO_USER: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG }} DATABASE_NAME: ${{ steps.setup.outputs.database_name }} ENGINE_NAME: ${{ steps.setup.outputs.engine_name }} STOPPED_ENGINE_NAME: ${{ steps.setup.outputs.stopped_engine_name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8a972d18f49..c4e720f2b7f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,6 +16,8 @@ jobs: secrets: FIREBOLT_CLIENT_ID_STG_NEW_IDN: ${{ secrets.FIREBOLT_CLIENT_ID_STG_NEW_IDN }} FIREBOLT_CLIENT_SECRET_STG_NEW_IDN: ${{ secrets.FIREBOLT_CLIENT_SECRET_STG_NEW_IDN }} + ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG }} + ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG }} publish: runs-on: ubuntu-latest diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index c47b383a827..30c524047f8 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -17,6 +17,10 @@ API_ENDPOINT_ENV = "API_ENDPOINT" SERVICE_ID_ENV = "SERVICE_ID" SERVICE_SECRET_ENV = "SERVICE_SECRET" +# Pre-provisioned service account that has no user attached, used to check +# that such an account cannot connect +SERVICE_ID_NO_USER_ENV = "SERVICE_ID_NO_USER" +SERVICE_SECRET_NO_USER_ENV = "SERVICE_SECRET_NO_USER" CORE_URL_ENV = "CORE_URL" # Only these values are safe to print. Anything else, including renamed @@ -130,6 +134,13 @@ def auth(service_id: str, service_secret: Secret) -> ClientCredentials: return ClientCredentials(service_id, service_secret.value) +@fixture(scope="session") +def auth_no_user() -> ClientCredentials: + return ClientCredentials( + must_env(SERVICE_ID_NO_USER_ENV), must_env(SERVICE_SECRET_NO_USER_ENV) + ) + + @fixture(scope="session") def core_auth() -> FireboltCore: return FireboltCore() diff --git a/tests/integration/dbapi/async/V2/conftest.py b/tests/integration/dbapi/async/V2/conftest.py index 65a061d4f75..57f443f80ab 100644 --- a/tests/integration/dbapi/async/V2/conftest.py +++ b/tests/integration/dbapi/async/V2/conftest.py @@ -1,5 +1,3 @@ -import random -import string from typing import Any, Callable, Tuple from pytest import fixture @@ -7,8 +5,6 @@ import firebolt.async_db from firebolt.async_db import Connection, connect from firebolt.client.auth.base import Auth -from firebolt.client.auth.client_credentials import ClientCredentials -from tests.integration.conftest import Secret @fixture @@ -96,42 +92,6 @@ async def connection_system_engine_no_db( yield connection -@fixture -async def service_account_no_user( - connection_system_engine_no_db: Connection, - database_name: str, -) -> Tuple[str, Secret]: - # function-level fixture so we need to make sa name is unique - randomness = "".join(random.choices(string.ascii_letters + string.digits, k=2)) - sa_account_name = f"{database_name}_no_user_{randomness}" - async with connection_system_engine_no_db.cursor() as cursor: - await cursor.execute( - f'CREATE SERVICE ACCOUNT "{sa_account_name}" ' - "WITH DESCRIPTION = 'Ecosytem test with no user'" - ) - await cursor.execute(f"CALL fb_GENERATESERVICEACCOUNTKEY('{sa_account_name}')") - # service_account_name, service_account_id, secret - _, s_id, key = await cursor.fetchone() - # Currently this is bugged so retrieve id via a query. FIR-28719 - if not s_id: - await cursor.execute( - "SELECT service_account_id FROM information_schema.service_accounts " - f"WHERE service_account_name='{sa_account_name}'" - ) - s_id = (await cursor.fetchone())[0] - # Wrap in secret to avoid leaking the key in the logs - yield s_id, Secret(key) - await cursor.execute(f'DROP SERVICE ACCOUNT "{sa_account_name}"') - - -@fixture -async def auth_no_user( - service_account_no_user: Tuple[str, Secret], -) -> ClientCredentials: - s_id, s_secret = service_account_no_user - return ClientCredentials(s_id, s_secret.value) - - @fixture async def mixed_case_db_and_engine( connection_system_engine: Connection, diff --git a/tests/integration/dbapi/sync/V2/conftest.py b/tests/integration/dbapi/sync/V2/conftest.py index b8b1e3ab107..56c5a248a41 100644 --- a/tests/integration/dbapi/sync/V2/conftest.py +++ b/tests/integration/dbapi/sync/V2/conftest.py @@ -1,14 +1,10 @@ -import random -import string from typing import Any, Callable, Tuple from pytest import fixture import firebolt.db from firebolt.client.auth.base import Auth -from firebolt.client.auth.client_credentials import ClientCredentials from firebolt.db import Connection, connect -from tests.integration.conftest import Secret @fixture @@ -104,42 +100,6 @@ def connection_system_engine_no_db( yield connection -@fixture -def service_account_no_user( - connection_system_engine_no_db: Connection, - database_name: str, -) -> Tuple[str, Secret]: - # function-level fixture so we need to make sa name is unique - randomness = "".join(random.choices(string.ascii_letters + string.digits, k=2)) - sa_account_name = f"{database_name}_no_user_{randomness}" - with connection_system_engine_no_db.cursor() as cursor: - cursor.execute( - f'CREATE SERVICE ACCOUNT "{sa_account_name}" ' - "WITH DESCRIPTION = 'Ecosytem test with no user'" - ) - cursor.execute(f"CALL fb_GENERATESERVICEACCOUNTKEY('{sa_account_name}')") - # service_account_name, service_account_id, secret - _, s_id, key = cursor.fetchone() - # Currently this is bugged so retrieve id via a query. FIR-28719 - if not s_id: - cursor.execute( - "SELECT service_account_id FROM information_schema.service_accounts " - f"WHERE service_account_name='{sa_account_name}'" - ) - s_id = cursor.fetchone()[0] - # Wrap in secret to avoid leaking the key in the logs - yield s_id, Secret(key) - cursor.execute(f'DROP SERVICE ACCOUNT "{sa_account_name}"') - - -@fixture -def auth_no_user( - service_account_no_user: Tuple[str, Secret], -) -> ClientCredentials: - s_id, s_secret = service_account_no_user - return ClientCredentials(s_id, s_secret.value) - - @fixture def mixed_case_db_and_engine( connection_system_engine: Connection,