module

Krikri::PluginHelpers::MysqlPrivileges

MysqlPrivileges - pure logic for parsing/comparing MySQL privilege grants. No I/O - mysql_user.cr does the actual GRANT/REVOKE calls.

Class methods

current_grants(show_grants_lines : Enumerable(String)) : Hash(String, Set(String))

Reduces a full SHOW GRANTS result to the same {target => privileges} shape parse_spec produces, for direct comparison. Multiple GRANT lines for the same target (e.g. MySQL's static+dynamic privilege split on .) are merged - mysql_info uses parse_show_grants_line directly when it needs the per-line order instead.

Source
desired_grants(spec : String) : Hash(String, Set(String))
Source
grant_parts(privileges : Array(String)) : Tuple(String, String)

The privilege list and WITH GRANT OPTION clause for a GRANT statement. GRANT is the grant-option pseudo-privilege, never a name in the list ("ALL, GRANT" is a syntax error on MariaDB); an otherwise empty list is USAGE.

Source
parse_show_grants_line(line : String) : Grant | Nil

Parses one line of real SHOW GRANTS FOR user@host output, e.g. GRANT SELECT, INSERT ON \db`.* TO `user`@`host`, optionally suffixed WITH GRANT OPTION(mapped to a "GRANT" pseudo-privilege, matching Ansible's own priv: convention for the grant option). The baselineGRANT USAGE ON . TO ...` identity row every MySQL/MariaDB account has is returned like any other grant (as a {"USAGE"} privilege set): real community.mysql keeps it too (privileges_get), and mysql_user's idempotency comparison needs it - a desired spec of ".:USAGE" must compare equal to the baseline row, not look like a missing grant.

Source
parse_spec(spec : String) : Array(Grant)

Parses a priv: param string in Ansible's own format: "db.table:PRIV1,PRIV2/db2.table2:PRIV3" (multiple grants separated by "/", privileges within one grant separated by ",").

Source
plan_changes(desired : Hash(String, Set(String)), current : Hash(String, Set(String)), append_privs : Bool, module_user : String | Nil = nil) : Array(Op)

Plans the GRANT/REVOKE statements needed to bring an account's current grants to the desired spec, the way real community.mysql decides them (its user_add/user_mod privilege branch):

  • targets granted today that the spec doesn't mention at all are revoked only when replacing (append_privs: false);
  • on shared targets, append_privs only ever GRANTs the missing privileges and never revokes; replacing also revokes the extras;
  • a grant of ALL makes revoking the leftovers pointless except the grant option (real's own shortcut);
  • WITH GRANT OPTION cannot stand alone, so a grant of only the GRANT pseudo-privilege becomes USAGE + WITH GRANT OPTION. module_user mirrors real's user != "root" guard: for an account literally named root the revoke-everything loop is skipped entirely (live-verified, MySQL 8.0 - real never revokes the unmentioned grants of an account called root).

The grant-option handling inside the revoke loop reproduces real's own leak: its grant_option flag is set once any iterated target carries GRANT and is never reset, so the REVOKE GRANT OPTION for a later target fires even when that target itself holds no option - live-verified on MySQL 8.4, where the server then rejects the statement with error 1141 and the task fails, while 8.0 tolerates it (krikri used to plan the option revoke only for targets that actually hold it, which diverges from real on MariaDB and 8.4). Iteration is in SHOW GRANTS row order (real iterates the dict privileges_get built in exactly that order), not sorted. Returns [] when the account already matches - mysql_user's idempotency test is exactly "is the plan empty".

Source
priv_change_msg(desired : Hash(String, Set(String)), current : Hash(String, Set(String)), append_privs : Bool, module_user : String) : String | Nil

The success msg real community.mysql's user_mod produces for the privilege part of an update, derived from the same three loops its privilege handling runs (in the same order - last assignment wins, which is what makes a combined revoke+grant report the intersect branch's wording):

  • targets the spec drops entirely (replace mode): "Privileges updated" - skipped for an account literally named root (its user != "root" guard) and for PROXY-only grants (krikri never parses GRANT PROXY lines, so that leg cannot fire);
  • targets the spec adds: "New privileges granted";
  • shared targets with a diff: "Privileges updated: granted [...], revoked [...]" - the lists are real's own grant_privs/revoke_privs BEFORE execution filtering: GRANT stays in the lists (live: "granted ['GRANT', 'USAGE']" / "revoked ['GRANT']"), a GRANT-only grant shows the appended USAGE, and the ALL shortcut empties the revokes down to the grant option. nil means no privilege wording - the caller keeps whatever earlier branch (password/plugin/auth) produced. The element order inside the lists is krikri's deterministic sorted order; real's comes from Python set iteration and varies between its own runs, so sorted is one of the orders real itself can produce.
Source
pylist(items : Enumerable(String)) : String

Python's repr of a list of strings, the exact shape real community.mysql interpolates into its "Privileges updated: granted [...], revoked [...]" msg (its own lists come out of Python set arithmetic, so it renders them with repr()).

Source

Nested types