module

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

DEFAULT_OPTION_FILE = "~/.my.cnf"

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.

DEFAULT_SOCKET_CANDIDATES = ["/var/run/mysqld/mysqld.sock", "/run/mysqld/mysqld.sock", "/var/lib/mysql/mysql.sock", "/tmp/mysql.sock"]

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.

MARIADB_WARNING = "MariaDB has been detected: its support will be dropped in 6.0.0. For MariaDB automation, please use the ansible.mariadb collection instead."

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.

MYSQL_DRIVER_FAIL_MSG = "A MySQL module is required: for Python 2.7 either PyMySQL, or MySQL-python, or for Python 3.X mysqlclient or PyMySQL. Consider setting ansible_python_interpreter to use the intended Python version."

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.

MYSQLDB_CONNECTOR_WARNING = "Support of mysqlcline/MySQLdb connector is deprecated. We'll stop testing against it in collection version 4.0.0 and remove the related code in 5.0.0. Use PyMySQL connector instead."

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.

PACKAGE_ROOT_GLOBS = ["/usr/lib/python3/dist-packages", "/usr/lib/python3*/site-packages", "/usr/local/lib/python3*/dist-packages", "/usr/local/lib/python3*/site-packages", "/usr/lib64/python3*/site-packages"]

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

add_mariadb_warning(result : PluginResult, version : String) : PluginResult
Source
add_mysqldb_connector_warning(result : PluginResult, package_dirs : Array(String) = connector_package_dirs, exists : String -> Bool = ->(path : String) do File.exists?(path) end) : PluginResult

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).

Source
build_uri(host : String | Nil = nil, port : String | Nil = nil, user : String | Nil = nil, password : String | Nil = nil, unix_socket : String | Nil = nil, config_file : String | Nil = nil, db : String | Nil = nil) : String
Source
connector_package_dirs(glob : String -> Array(String) = ->(pattern : String) do Dir.glob(pattern) end) : Array(String)

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.

Source
default_socket_file(exists : String -> Bool = ->(path : String) do File.exists?(path) end) : String | Nil

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.

Source
mysql_driver_importable?(vars : Hash(String, JSON::Any) | Nil = nil, interpreter : String | Nil = nil) : Bool

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.

Source
mysql_driver_missing_result(vars : Hash(String, JSON::Any) | Nil = nil, interpreter : String | Nil = nil) : PluginResult | Nil

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.

Source
mysqldb_connector_on_host?(package_dirs : Array(String) = connector_package_dirs, exists : String -> Bool = ->(path : String) do File.exists?(path) end) : Bool

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.

Source
option_file_defaults(config_file : String | Nil) : NamedTuple(user: String | Nil, password: String | Nil, socket: String | Nil)
Source
resolve_option_file_path(config_file : String) : String | Nil

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.

Source
resolve_socket(unix_socket : String | Nil, host : String | Nil, defs : NamedTuple(user: String | Nil, password: String | Nil, socket: String | Nil), exists : String -> Bool = ->(path : String) do File.exists?(path) end) : String | Nil

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.

Source
strip_quotes(value : String) : String
Source