module

Krikri::PluginHelpers::URLPreflight

URLPreflight - the failures Ansible's fetch_url reports BEFORE urllib ever opens the URL, shared by the uri and get_url plugins.

Ansible builds a request in three steps that all happen before a single byte goes out (the Ansible module's Request.open, 2.19.11):

  1. _configure_auth() - with use_gssapi: true the module imports python-gssapi, and a host without it raises MissingModuleError (fetch_url's own except MissingModuleError handler, whose result carries the message ALONE - no url, no status).
  2. make_context() - opens ca_path (ssl.create_default_context's cafile=), applies ciphers: (set_ciphers on the joined list) and loads client_cert:/client_key: (load_cert_chain). A missing or unusable file raises an OSError, an unusable cipher list an SSLError; both are OSError subclasses, so fetch_url's except OSError handler folds them into info['msg'] = "Connection failure: <str(ex)>" with status -1.
  3. urllib.request.Request(url) - a URL with no type at all raises ValueError("unknown url type: '<url>'"), which fetch_url re-raises through fail_json(msg=..., **info) with info = {url, status: -1}. A URL that DOES have a type but no handler (gopher://..., "127.0.0.1:80/x" - Python's type split does not require an RFC scheme) instead dies later, in urllib's own UnknownHandler, with URLError('unknown url type: <type>') - so it comes back as info['msg'] = "Request failed: <urlopen error unknown url type: <type>>" with status -1, i.e. the same info dict the OSError half below produces.

The order matters as much as the checks: make_context runs before the URL is parsed, so a task carrying BOTH a bogus ciphers: list and a scheme-less url reports the cipher failure. Each module's own module-side logic (get_url's dest/checksum skip, uri's creates:/removes: skip) runs even earlier - see the call sites.

Deliberately NOT emulated: the content-level OpenSSL errors a non-PEM (or empty) ca_path/client_cert raises ("[SSL] PEM lib (_ssl.c:4093)", "[X509: NO_CERTIFICATE_OR_CRL_FOUND] ...") - those carry CPython source line numbers and vary per interpreter build, so matching them would be guesswork. A readable file of any content passes the preflight and fails (or works) at handshake time, exactly as the real request would.

Constants

URL_TYPES = ["http", "https", "ftp", "file", "data"] of ::String

The URL types urllib's default opener actually has a handler for (urllib.request's own default handler list).

Class methods

check(url : String, ciphers : String | Nil = nil, ca_path : String | Nil = nil, client_cert : String | Nil = nil, client_key : String | Nil = nil, use_gssapi : Bool = false) : Failure | Nil

The ciphers is the OpenSSL cipher-list string the real params join into ("all ciphers are joined in order with ':'", get_url's own docs) - nil or empty means "don't set them", in which case Ansible skips set_ciphers entirely.

Source

Nested types