Skip to content

feat: add database grants and connectivity check#19

Merged
konradmichalik merged 7 commits intomainfrom
feature/requirements-database-grants
Feb 19, 2026
Merged

feat: add database grants and connectivity check#19
konradmichalik merged 7 commits intomainfrom
feature/requirements-database-grants

Conversation

@konradmichalik
Copy link
Contributor

@konradmichalik konradmichalik commented Feb 19, 2026

Summary

  • Add requirements:check:database_grants task that validates database user permissions based on database_manager_type
  • Root mode: Connects to the database, runs SHOW GRANTS, and verifies all required grants exist on *.*
  • Simple mode: Validates pool configuration and tests connectivity to each pool database
  • Mittwald API: Skipped (managed via API)
  • Credential resolution without interactive prompts (Deployer config → env vars → shared .env file)
  • Add grant parsing with ALL PRIVILEGES shortcut support

Changes

  • autoload.php — Register new task file
  • deployer/requirements/config/set.php — Add requirements_check_database_grants_enabled toggle
  • deployer/requirements/functions.php — Add REQUIREMENT_DATABASE_GRANTS constant, resolveDatabaseCredentials(), parseGlobalGrants()
  • deployer/requirements/task/check_database_grants.php — New task with Root and Simple mode checks
  • deployer/requirements/task/list.php — Display database grants info in requirements:list
  • deployer/requirements/task/requirements.php — Register task in check pipeline
  • docs/DATABASE.md — Improve Root mode docs with SQL grant example
  • docs/REQUIREMENTS.md — Add database grants check documentation

Summary by CodeRabbit

  • New Features

    • Added a database grants verification to pre-deployment requirements, covering Root, Simple, and Mittwald API modes, including connectivity and per-pool checks and an opt-in configuration flag.
  • Documentation

    • Updated requirements and database docs with the new database grants section, SQL grant example, configuration flag, and credential resolution guidance.

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Adds a new database-grants requirement check: credential resolution, grant parsing, and connectivity verification across Root, Simple (pool), and Mittwald API modes, plus config toggle, autoload registration, requirements-list display, and documentation updates.

Changes

Cohort / File(s) Summary
Autoload & Config
autoload.php, deployer/requirements/config/set.php
Register new requirements check in autoload and add config flag requirements_check_database_grants_enabled.
Requirements Task Integration
deployer/requirements/task/requirements.php, deployer/requirements/task/list.php
Include requirements:check:database_grants in check sequence; add conditional "Database Grants" section in the requirements listing (modes: Root, Simple, Mittwald API).
Grant-check Implementation
deployer/requirements/functions.php, deployer/requirements/task/check_database_grants.php
Add REQUIREMENT_DATABASE_GRANTS constant, credential resolver (resolveDatabaseCredentials()), grant parser (parseGlobalGrants()), and new task logic (checkRootGrants(), checkSimplePoolConnectivity()) handling connectivity, SHOW GRANTS parsing, and reporting.
Documentation
docs/DATABASE.md, docs/REQUIREMENTS.md
Document required global grants (SQL block + note about .), rename "Prerequirements" → "Prerequisites", and document the new database grants check and config flag.

Sequence Diagram

sequenceDiagram
    participant Task as Database Grants Task
    participant Creds as Credential Resolver
    participant MySQL as MySQL Client
    participant Parser as Grant Parser
    participant Report as Requirements Reporter

    Task->>Creds: resolveDatabaseCredentials()
    Creds->>Creds: check Deployer config, env vars, shared .env
    Creds-->>Task: credentials or null

    alt credentials resolved
        Task->>MySQL: connect (binary/host/port/user)
        MySQL-->>Task: connection/result
        Task->>MySQL: SHOW GRANTS FOR CURRENT_USER()
        MySQL-->>Task: grants output
        Task->>Parser: parseGlobalGrants(output)
        Parser-->>Task: missing grants / ok
        Task->>Report: mark success or failure with details
    else no creds
        Task->>Report: mark skipped
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hopped through grants with whiskers bright,
Checking users in the quiet night,
From root to pools I scanned each line,
All set to deploy — the stars align!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: adding a new database grants and connectivity check task to the requirements validation pipeline.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/requirements-database-grants

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
deployer/requirements/config/set.php (1)

20-20: Nit: Move the toggle into the existing block for consistency.

All other _enabled toggles are grouped together (lines 11–19). This new one is separated by a blank line, breaking the visual grouping.

📝 Proposed fix
 set('requirements_check_env_enabled', true);
 set('requirements_check_eol_enabled', true);
-
-set('requirements_check_database_grants_enabled', true);
+set('requirements_check_database_grants_enabled', true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deployer/requirements/config/set.php` at line 20, The new toggle
set('requirements_check_database_grants_enabled', true) is separated from the
other _enabled toggles; move this set call into the existing block where the
other `_enabled` settings are declared (the grouped toggle block containing the
other set(..., true/false) entries) so all `_enabled` flags are visually
adjacent and consistent.
deployer/requirements/task/check_database_grants.php (1)

106-118: array_filter($config, 'is_string') silently drops non-string values, which may mask misconfiguration.

If a pool entry has e.g. 'database_name' => null or 'database_name' => 123, it will be reported as "Missing config: database_name" rather than "Invalid type for database_name". The current behavior is acceptable (a non-string value is effectively missing for this purpose), but worth noting if you want more precise diagnostics in the future.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deployer/requirements/task/check_database_grants.php` around lines 106 - 118,
The loop over $pool uses array_filter($config, 'is_string') which removes keys
with non-string values and causes all non-string entries to be reported as
"missing"; update the validation in the foreach that builds $missingKeys to
explicitly check each required key for existence and type (e.g., use
isset/array_key_exists and is_string) so you can distinguish missing keys from
invalid-typed keys, and then call addRequirementRow with either "Missing config:
..." or "Invalid type for: ..." as appropriate; refer to the $pool foreach,
$requiredKeys, $missingKeys, and addRequirementRow locations to implement this
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@deployer/requirements/functions.php`:
- Around line 408-464: In resolveDatabaseCredentials(): when credentials are
populated from getSharedEnvVars() for TYPO3 or Symfony also resolve host (and
port for Symfony) so remote DBs work; for TYPO3 read
'TYPO3_CONF_VARS__DB__Connections__Default__host' from $envVars if $host is
still default and keep port at 3306, and for Symfony parse DATABASE_URL with
parse_url(..., PHP_URL_HOST) and parse_url(..., PHP_URL_PORT) (apply
urldecode/appropriate casts) to set $host and, if present, $port; ensure
existing logic for $user/$password (including env_key_database_passwort
handling) remains unchanged and that you only override $host/$port when they are
unset or still the default.

In `@docs/DATABASE.md`:
- Line 9: The document has inconsistent section headings: the root section uses
"Prerequisites" but the "Simple" and "Mittwald API" subsections are still titled
"Prerequirements"; update those subsection headings to read "Prerequisites"
instead of "Prerequirements" so all sections use the same spelling (look for the
headings under "Simple" and "Mittwald API" and replace "Prerequirements" with
"Prerequisites").

In `@docs/REQUIREMENTS.md`:
- Around line 68-72: Update the fenced code block that lists SQL privilege names
to include a language identifier; change the opening triple backticks to ```sql
so the block becomes a SQL code block (this will satisfy markdownlint MD040 for
the block containing "SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX,
ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW,
CREATE ROUTINE, ALTER ROUTINE").

---

Nitpick comments:
In `@deployer/requirements/config/set.php`:
- Line 20: The new toggle set('requirements_check_database_grants_enabled',
true) is separated from the other _enabled toggles; move this set call into the
existing block where the other `_enabled` settings are declared (the grouped
toggle block containing the other set(..., true/false) entries) so all
`_enabled` flags are visually adjacent and consistent.

In `@deployer/requirements/task/check_database_grants.php`:
- Around line 106-118: The loop over $pool uses array_filter($config,
'is_string') which removes keys with non-string values and causes all non-string
entries to be reported as "missing"; update the validation in the foreach that
builds $missingKeys to explicitly check each required key for existence and type
(e.g., use isset/array_key_exists and is_string) so you can distinguish missing
keys from invalid-typed keys, and then call addRequirementRow with either
"Missing config: ..." or "Invalid type for: ..." as appropriate; refer to the
$pool foreach, $requiredKeys, $missingKeys, and addRequirementRow locations to
implement this change.

Also fix inconsistent "Prerequirements" headings in DATABASE.md
and add sql language identifier to REQUIREMENTS.md code block.
@konradmichalik
Copy link
Contributor Author

Regarding the two nitpick comments from the review:

config/set.php toggle grouping: The toggle is already adjacent to the other _enabled flags (lines 19-20) with no blank line separating them. No change needed.

array_filter($config, 'is_string') in pool validation: As noted in the review, the current behavior is acceptable — a non-string value is effectively missing for configuration purposes. Keeping the simpler approach.

@konradmichalik konradmichalik merged commit 8a7ec65 into main Feb 19, 2026
1 of 2 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
deployer/requirements/functions.php (1)

513-515: explode(', ', ...) is brittle if MySQL omits the space after commas.

Standard MySQL/MariaDB output always uses ', ' so in practice this works, but a more defensive split handles any whitespace variation:

♻️ Proposed refactor
-        foreach (explode(', ', $grantsStr) as $grant) {
-            $globalGrants[] = trim($grant);
-        }
+        foreach (array_map('trim', explode(',', $grantsStr)) as $grant) {
+            if ($grant !== '') {
+                $globalGrants[] = $grant;
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deployer/requirements/functions.php` around lines 513 - 515, The loop that
splits $grantsStr uses explode(', ', $grantsStr) which fails if MySQL omits the
space; change the split to be whitespace-tolerant (e.g. use
preg_split('/\s*,\s*/', $grantsStr) or explode(',', $grantsStr) combined with
array_map('trim', ...) ) and keep pushing trimmed entries into $globalGrants
(the foreach that currently iterates over explode(', ', $grantsStr) and calls
trim($grant) should be updated accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@deployer/requirements/functions.php`:
- Around line 423-476: The credential guard (if ($user === '' || $password ===
'')) prevents reading shared env vars so host/port resolution for typ o3/symfony
never runs when credentials are pre-resolved; change the logic so host/port are
resolved independently: call getSharedEnvVars() when $host === '127.0.0.1' or
$port === 3306 (or when $host/$port are defaults) and apply the TYPO3
(TYPO3_CONF_VARS__DB__Connections__Default__host) and Symfony (DATABASE_URL
parse_url PHP_URL_HOST / PHP_URL_PORT) resolution outside or in addition to the
existing ($user/$password) block, reusing the same $envVars and keeping the
existing keys (env_key_database_passwort, DATABASE_URL) and parse_url usage.

---

Nitpick comments:
In `@deployer/requirements/functions.php`:
- Around line 513-515: The loop that splits $grantsStr uses explode(', ',
$grantsStr) which fails if MySQL omits the space; change the split to be
whitespace-tolerant (e.g. use preg_split('/\s*,\s*/', $grantsStr) or
explode(',', $grantsStr) combined with array_map('trim', ...) ) and keep pushing
trimmed entries into $globalGrants (the foreach that currently iterates over
explode(', ', $grantsStr) and calls trim($grant) should be updated accordingly).

Comment on lines +423 to +476
if ($user === '' || $password === '') {
$envVars = getSharedEnvVars();

if (has('app_type') && get('app_type') === 'typo3') {
if ($user === '') {
$user = $envVars['TYPO3_CONF_VARS__DB__Connections__Default__user'] ?? '';
}

if ($password === '') {
$key = has('env_key_database_passwort')
? get('env_key_database_passwort')
: 'TYPO3_CONF_VARS__DB__Connections__Default__password';
$password = $envVars[$key] ?? '';
}

if ($host === '127.0.0.1') {
$envHost = $envVars['TYPO3_CONF_VARS__DB__Connections__Default__host'] ?? '';

if ($envHost !== '') {
$host = $envHost;
}
}
} elseif (has('app_type') && get('app_type') === 'symfony') {
$databaseUrl = $envVars['DATABASE_URL'] ?? '';

if ($databaseUrl !== '') {
if ($user === '') {
$parsed = parse_url($databaseUrl, PHP_URL_USER);
$user = is_string($parsed) ? urldecode($parsed) : '';
}

if ($password === '') {
$parsed = parse_url($databaseUrl, PHP_URL_PASS);
$password = is_string($parsed) ? urldecode($parsed) : '';
}

if ($host === '127.0.0.1') {
$parsed = parse_url($databaseUrl, PHP_URL_HOST);

if (is_string($parsed) && $parsed !== '') {
$host = $parsed;
}
}

if ($port === 3306) {
$parsed = parse_url($databaseUrl, PHP_URL_PORT);

if (is_int($parsed)) {
$port = $parsed;
}
}
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

$host stays 127.0.0.1 when both credentials are pre-resolved before reaching the .env block.

The guard at line 423 ($user === '' || $password === '') prevents .env from being read when both credentials are already satisfied — for example, database_user set in the Deployer config and database_password supplied via DEPLOYER_CONFIG_DATABASE_PASSWORD. In that case getSharedEnvVars() is never called, so the TYPO3/Symfony host resolution on lines 438–464 is never reached and $host remains 127.0.0.1. For projects where the DB host is only in the shared .env (remote RDS/MariaDB endpoint), the grants check will silently attempt to connect to the wrong host.

The TYPO3/Symfony host-from-env logic added inside the block already handles the case where at least one credential is missing (addressing the earlier review). The remaining gap is when credentials are fully pre-resolved but database_host is not explicitly configured.

🛠️ Proposed fix

Decouple host/port resolution from the credential guard by checking whether defaults are still in place independently:

+    $needsHostFromEnv = !has('database_host') || $host === '127.0.0.1';
+    $needsPortFromEnv = !has('database_port') || $port === 3306;
+
-    if ($user === '' || $password === '') {
+    if ($user === '' || $password === '' || $needsHostFromEnv || $needsPortFromEnv) {
         $envVars = getSharedEnvVars();
 
         if (has('app_type') && get('app_type') === 'typo3') {
             if ($user === '') {
                 $user = $envVars['TYPO3_CONF_VARS__DB__Connections__Default__user'] ?? '';
             }
 
             if ($password === '') {
                 $key = has('env_key_database_passwort')
                     ? get('env_key_database_passwort')
                     : 'TYPO3_CONF_VARS__DB__Connections__Default__password';
                 $password = $envVars[$key] ?? '';
             }
 
-            if ($host === '127.0.0.1') {
+            if ($needsHostFromEnv) {
                 $envHost = $envVars['TYPO3_CONF_VARS__DB__Connections__Default__host'] ?? '';
 
                 if ($envHost !== '') {
                     $host = $envHost;
                 }
             }
         } elseif (has('app_type') && get('app_type') === 'symfony') {
             $databaseUrl = $envVars['DATABASE_URL'] ?? '';
 
             if ($databaseUrl !== '') {
                 if ($user === '') {
                     $parsed = parse_url($databaseUrl, PHP_URL_USER);
                     $user = is_string($parsed) ? urldecode($parsed) : '';
                 }
 
                 if ($password === '') {
                     $parsed = parse_url($databaseUrl, PHP_URL_PASS);
                     $password = is_string($parsed) ? urldecode($parsed) : '';
                 }
 
-                if ($host === '127.0.0.1') {
+                if ($needsHostFromEnv) {
                     $parsed = parse_url($databaseUrl, PHP_URL_HOST);
 
                     if (is_string($parsed) && $parsed !== '') {
                         $host = $parsed;
                     }
                 }
 
-                if ($port === 3306) {
+                if ($needsPortFromEnv) {
                     $parsed = parse_url($databaseUrl, PHP_URL_PORT);
 
                     if (is_int($parsed)) {
                         $port = $parsed;
                     }
                 }
             }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deployer/requirements/functions.php` around lines 423 - 476, The credential
guard (if ($user === '' || $password === '')) prevents reading shared env vars
so host/port resolution for typ o3/symfony never runs when credentials are
pre-resolved; change the logic so host/port are resolved independently: call
getSharedEnvVars() when $host === '127.0.0.1' or $port === 3306 (or when
$host/$port are defaults) and apply the TYPO3
(TYPO3_CONF_VARS__DB__Connections__Default__host) and Symfony (DATABASE_URL
parse_url PHP_URL_HOST / PHP_URL_PORT) resolution outside or in addition to the
existing ($user/$password) block, reusing the same $envVars and keeping the
existing keys (env_key_database_passwort, DATABASE_URL) and parse_url usage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments