Connection module#
The __init__.py of a resource type is the connection module.
It owns three jobs:
Tell the managing minion which resource ids it manages (
discover).Establish whatever per-resource state is needed before any call (
init+initialized).Produce per-resource grains (
grains+grains_refresh).
A handful of optional hooks (ping, shutdown, custom execution
functions) round it out.
Required interface#
Every connection module must define:
__virtual__()Standard Salt loader hook. Return
True(or your virtualname) if the type can be loaded on this minion; return(False, "reason")to opt out. Resource types typically returnTrueunconditionally — the loader is only consulted when the pillar names the type.init(opts)Called once when the type is loaded, before any per-resource operations run. Reads the resource type's pillar subtree and seeds any shared state in
__context__. Idempotent.initialized()Returns
Trueifinit()has run successfully. The framework checks this before dispatching per-resource operations so a partialinitfailure doesn't produce bogus results.discover(opts)Returns the list of bare resource ids (not full SRNs) that this minion manages for this type. Called by
saltutil.refresh_resourcesand by minion startup. Read from pillar — typically theresource_idskey under the type's subtree.grains()Returns the grain dict for the currently dispatched resource. The current resource is in
__resource__(see Dunders below). What you put here is what-G key:valuewill match against; keep it small and tag-like (env, role, region) rather than re-reading the world on every target check.
Optional interface#
ping()Reachability probe for the current resource. Used by
salt.modules.test.ping()overrides and operator tools.grains_refresh()Invalidate any cached grain state and recompute. The default loader calls
grains()again if you don't implement this; implement it only if you maintain your own cache layer.shutdown(opts)Tear down type-level state from
__context__. Called when the minion shuts down or unloads the type.
Anything else you define in __init__.py is callable as a
per-resource execution function once the loader picks the module up.
Functions named with a verb-noun convention
(service_start, package_install) tend to age well; if you want
them to take over a slot in the standard Salt module surface, put the
overrides in modules/ instead (see
Execution module overrides).
Dunders available in connection-module code#
The loader packs these into your module's globals before any call:
__opts__The managing minion's opts dict — same as anywhere else in Salt.
__context__Per-loader transient dict. Use it for connection caches and the
initializedflag.__resource__{"type": "<rtype>", "id": "<resource-id>"}. Set for every per-resource dispatch (grains, ping, custom funcs). Not set duringinit,discover, orshutdown— those run once per type, not once per resource.__pillar__,__grains__The managing minion's pillar and grains. The resource's own grains aren't yet available inside the connection module itself — they're what it produces.
__salt__The managing minion's standard execution-module loader. Useful for delegating to
cmd.runor any other Salt function on the host that's doing the connecting.
Pattern: filesystem-backed dummy#
The reference implementation in salt.resources.dummy is a
fully self-contained example. It persists per-resource state to a
cache file and is wired up so that salt -C 'T@dummy' state.apply
exercises every code path without needing real connectivity. Read it
when you start your own type.
Pattern: connection-per-resource cached in __context__#
For types that talk to a real remote service, build the connection lazily and cache it keyed by resource id:
def _connect(resource_id):
conns = __context__.setdefault("widget", {}).setdefault("conns", {})
if resource_id not in conns:
cfg = (
salt.utils.resources.pillar_resources_tree(__opts__)
.get("widget", {})
.get("hosts", {})
.get(resource_id, {})
)
conns[resource_id] = WidgetClient(cfg)
return conns[resource_id]
def ping():
return _connect(__resource__["id"]).ping()
The cache lives for the lifetime of the per-type loader (i.e. until
the minion reloads modules or the type is unregistered). The loader
calls shutdown(opts) on teardown so you can close connections
cleanly.
Mistakes to avoid#
Doing slow work in
grains().grains()is called on every registration and refresh. If you need to phone the resource to get the value, cache it in__context__and refresh deliberately.Using
__resource__ininit().initruns once per type, before any resource is selected;__resource__is unset. Useoptsand the pillar instead.Mutating
opts. The opts dict is shared across the managing minion. Treat it as read-only — copy it if you need a per-resource variant.Leaking transient state in
__context__. Whatever you put in__context__persists across calls. Anything connection-scoped (auth tokens with TTLs, etc.) should track its own expiry.