Krikri::PluginHelpers::MysqlConnection
MysqlConnection - building a mysql:// connection URI from
Ansible-style login_* params, with the same implicit ~/.my.cnf
option-file fallback Ansible's own mysql_* modules rely on.
mysql_db/mysql_user/mysql_info/mysql_query do the actual DB.open.
Constants
The default option file every one of community.mysql's mysql_*
modules threads through as config_file: (its argument_spec
default is literally ~/.my.cnf; the module reads it whenever it
exists, via the client lib's read_default_file). Explicit login_*
params always win over it - it only fills in what a task didn't
pass explicitly.
Compiled-in default socket paths of the client libraries that resolve host 'localhost' to a socket (libmariadb/MySQLdb), per packaging family. Debian/Ubuntu put the socket under /var/run/mysqld (/var/run -> /run symlink); RHEL-family MariaDB packages use /var/lib/mysql/mysql.sock; upstream tarballs/RPMs use /tmp/mysql.sock.
ansible.mysql's get_server_implementation() runs right after the
connection in mysql_db/mysql_info/mysql_query/mysql_variables and
module.warn()s whenever SELECT VERSION() contains "mariadb": the
text lands on the console (deduplicated per run) and in the task
result's warnings. mysql_user never calls it, so it stays silent.
Upstream's module-level import gate (ansible.mysql/community.mysql module_utils/mysql.py): every mysql_* module's bootstrap imports PyMySQL first and MySQLdb only as a fallback, and the module body fails with this exact fail_json - BEFORE any connection attempt, and before most of its own parameter handling - when neither import resolves. krikri's mysql_* plugins talk the wire protocol natively and would otherwise carry on to their own connect failure (or success) on a host where real ansible stops right here (stancel.create_mysql_dbs_and_users, issue #885). Byte-for-byte upstream's own constant, shared by both collections.
The [WARNING] real prints whenever one of the mysql_* modules connects
through the MySQLdb (mysqlclient) Python driver (issue #351,
fourforbusiness.shopware, community.mysql 4.x+ / ansible.mysql 5.x via
its plugin_routing redirect). Upstream bootstrap-imports PyMySQL
first and only falls back to import MySQLdb when PyMySQL is absent
anywhere, so MySQLdb alongside PyMySQL never warns. The warn call
sits inside mysql_connect(), BEFORE the driver's own connect raises,
so it rides every result a connection attempt produced (success,
the connection-refused fail_json, a later query failure) and reaches
the stderr console (deduplicated once per run, like every display
warning) and the result's warnings list. The text is upstream's
own byte-for-byte, including its "mysqlcline" typo (the warn string
in module_utils/mysql.py) and the stale "4.0.0"/"5.0.0" versions,
which that collection never actually shipped.
The Python package roots a connector module can be importable from on the distro layouts these role stacks run on: Debian/Ubuntu's system interpreter (python3-mysqldb installs MySQLdb under /usr/lib/python3/dist-packages), pip-installed site-packages for both system prefixes and versions, and RHEL's lib64 layout.
This approximates the target's python without shelling out to it. Known limit: an interpreter outside these roots (a venv) is not scanned at all, in both directions - a venv-hosted PyMySQL does not silence the warn for a lingering system MySQLdb, and a venv-hosted MySQLdb with nothing below the system roots does not warn. The apt-based connector installs these role stacks generate all land in these roots.
Class methods
Appends MYSQLDB_CONNECTOR_WARNING to result.extra["warnings"] when the host's connector resolves to MySQLdb. Call BEFORE add_mariadb_warning: upstream's warns are recorded in call order, mysql_connect's connector warn first and get_server_implementation's MariaDB one second, and the registered result keeps it (the same placement rule PostgresqlDeprecations.add_default_db_warning documents for AnsibleModule's warnings-then-deprecations list).
The roots that exist on this host, expanded from the patterns. glob
is injectable so tests can exercise the expansion without needing a
real dist-packages layout.
First default socket path that exists on this host, or nil when
none does. exists is injectable so tests can exercise the
candidate ordering without a real MySQL server's socket files.
The probe asks an interpreter instead of scanning package roots: the import the module payload performs resolves user-site (~/.local/lib/python3.X/site-packages), dist-packages and venv layouts a directory scan cannot see, and this dev host itself carries PyMySQL in user-site only - a scan would report the driver missing where real ansible imports it fine. The interpreter follows what the module would run under: the task's ansible_python_interpreter when pinned (auto* directives are discovery directives, not names - pip.cr's candidate order treats them the same way), then the discovery fact, then Krikri.discovered_target_python's INTERPRETER_FALLBACK order.
nil when the target's Python can import at least one of the two drivers upstream accepts - the module carries on - or the failed result carrying MYSQL_DRIVER_FAIL_MSG when neither imports.
True when the connector upstream's module bootstrap would resolve to
is MySQLdb: a MySQLdb package directory exists under at least one
package root and pymysql exists under none (PyMySQL is imported
first, so its presence silences this warning no matter where it
sits). package_dirs overrides the root set for scratch
directories; exists checks a package directory inside one.
Resolves a config_file path to an absolute path, expanding a leading
~ the same way Python's os.path.expanduser does (Ansible's
path-type params pass through that before any existence check).
Crystal's own File.expand_path does NOT expand ~ (it treats it as a
literal relative component), which is why this hand-rolls the tilde
expansion exactly like BasePlugin#expand_tilde does.
unix_socket, given, takes precedence over host/port (matches Ansible's mysql_db/mysql_user own login_unix_socket precedence).
Option-file handling mirrors community.mysql:
- config_file defaults to ~/.my.cnf, but is only read if the file actually exists (like upstream's os.path.exists config_file).
- Explicit login_user/login_password override the [client] user/password (upstream: "If login_user or login_password are given, they should override the config file").
- login_host/login_port are NOT taken from the option file (upstream keeps host=localhhost:3306 unless config_overrides_defaults:) - only user/password, plus socket as a fallback when no login_host was given, reach the URI. login_host/login_port never come from the config file. A config-file [client] socket is used when no login_unix_socket was given and the login host is the implicit/explicit 'localhost' (libmariadb's read_default_file [client] socket overrides its compiled-in default in exactly that situation).
Default-socket fallback (round 2300215, fiaasco.mariadb): when no login_unix_socket is given, community.mysql hands its default login_host of 'localhost' plus the port to the Python driver. On the Debian-family target stacks these roles actually run on, that driver is MySQLdb/libmariadb (the role installs python3-mysqldb; community.mysql prefers PyMySQL but it isn't present), and libmariadb maps a host of 'localhost' to its compiled-in unix-socket path, NOT to TCP. That is how real ansible bootstraps Debian's dual-auth root@localhost (native-password 'invalid' OR unix_socket) through the socket as the OS root user (SO_PEERCRED): live-verified on Ubuntu 24.04 + MariaDB 10.11, MySQLdb root/'' host='localhost' connects over the socket while host='127.0.0.1' is denied with errno 1698. Going TCP like PyMySQL would get 'Access denied' there, which is exactly what this engine did (all four mysql_user items of the role failed). The fallback only picks a candidate path that actually exists - if none does (no local server socket at a known location), we keep the TCP behavior so a pure-TCP server still connects.