module

Krikri::PythonLookupRunner

Constants

UNAVAILABLE_KIND = "unavailable"

The wrapper's response kind when the controller python3 cannot import the ansible package at all - the plugin file itself (which imports LookupBase at its top level) could never load either, so the whole mechanism is unavailable and the caller keeps the old "unknown lookup" behavior.

WRAPPER = "import importlib.util\nimport json\nimport sys\n\n\n_unavailable = None\ntry:\n from ansible.plugins.lookup import LookupBase\nexcept Exception as exc:\n _unavailable = \"%s: %s\" % (type(exc).__name__, exc)\n\n\ndef _load_plugin(path):\n modname = \"krikri_lookup_%d\" % hash(path)\n spec = importlib.util.spec_from_file_location(modname, path)\n if spec is None or spec.loader is None:\n raise ImportError(\"cannot load lookup plugin: %s\" % path)\n module = importlib.util.module_from_spec(spec)\n spec.loader.exec_module(module)\n return module\n\n\ndef _serialize(value):\n if isinstance(value, (set, frozenset)):\n return list(value)\n if isinstance(value, bytes):\n return value.decode(\"utf-8\", \"replace\")\n return str(value)\n\n\ndef main():\n payload = json.loads(sys.stdin.read())\n if _unavailable is not None:\n sys.stdout.write(json.dumps(\n {\"ok\": False, \"kind\": \"unavailable\",\n \"error\": \"ansible is not importable by the controller python3: %s\"\n % _unavailable}))\n return\n module = _load_plugin(payload.get(\"path\"))\n plugin_class = getattr(module, \"LookupModule\", None)\n if plugin_class is None or not isinstance(plugin_class, type) or \\\n not issubclass(plugin_class, LookupBase):\n sys.stdout.write(json.dumps(\n {\"ok\": False, \"kind\": \"not_found\",\n \"error\": \"no LookupModule subclass of LookupBase in %s\"\n % payload.get(\"path\")}))\n return\n instance = plugin_class()\n result = instance.run(payload.get(\"terms\") or [],\n variables=payload.get(\"variables\") or {},\n **(payload.get(\"kwargs\") or {}))\n sys.stdout.write(json.dumps({\"ok\": True, \"result\": result},\n default=_serialize))\n\n\ntry:\n main()\n# BaseException, not Exception: a plugin's `sys.exit()` raises\n# SystemExit, which would otherwise kill the wrapper before any\n# JSON response - reported as a kind \"error\" plugin failure here\n# rather than silently degrading the whole mechanism.\nexcept BaseException as exc:\n sys.stdout.write(json.dumps(\n {\"ok\": False, \"kind\": \"error\",\n \"error\": \"%s: %s\" % (type(exc).__name__, exc)}))"

The controller-side wrapper: imports the plugin file, finds its LookupModule class (required to subclass the real ansible.plugins.lookup.LookupBase, exactly like real Ansible's own loader check), instantiates it with the base class's own defaults, and calls run(terms, variables=..., **kwargs), printing the JSON-encoded result. Always responds with a single-line JSON object ({"ok": true, ...} / {"ok": false, kind, error}), so a plugin that prints its own noise cannot corrupt the protocol - only the LAST line of stdout is parsed. An ansible package the controller python3 cannot import reports kind "unavailable" (the whole mechanism degrades; the plugin file could not have imported its own base class either).

Instance methods

call_lookup(name : String, source : String, terms : Array(JSON::Any), variables : Hash(String, JSON::Any), kwargs : Hash(String, JSON::Any)) : JSON::Any

Invokes the LookupModule from the plugin source for name with the resolved terms/kwargs and the current task variables (real Ansible passes its own vars dict, which conventionally carries an omit key - see expression_evaluator's call site), returning the structured result list. Raises LookupError when the invocation fails (callers degrade to "undefined" only when the mechanism itself is unavailable - a dispatched failure is a real lookup error).

Source
find_source(name : String, role_path : String | Nil, playbook_dir : String | Nil) : String | Nil

A lookup_plugins/<name>.py source for name, searched in the role's own lookup_plugins/ first, then the playbook-adjacent one (nearest-first, same convention as PythonFilterRunner#find_sources). Unlike filters - whose names come from each FilterModule's filters() dict - real Ansible's plugin loader derives a lookup plugin's name from its FILE NAME, so no Python introspection (and no mtime cache) is needed here: lookup_plugins/manala_environment_files.py IS the manala_environment_files lookup, whatever class it declares.

Source
python_executable

The CONTROLLER's own python3 - the interpreter real Ansible needs on the controller anyway (same detection pattern as PythonFilterRunner#python_executable).

Source

Nested types