Idem 14.0.0#
Idem 14.0.0 introduces auto_state exec module contracts. Other contracts are also available for states and exec modules as described below.
Auto State#
The “auto_state” contract can be implemented for exec modules. It enforces that a get, list, create, update, and delete function exist for the exec module plugin with specific parameters and returns.
Implementing this contract allows idem to dynamically construct a state for the resource.
Here is an example of how to implement the auto_state
contract in an exec module plugin:
# /my_project_root/my_project/exec/my_cloud/my_resource.py
from typing import Any
from typing import Dict
__contracts__ = ["auto_state"]
__func_alias__ = {"list_": "list"}
async def get(hub, ctx, name, **kwargs) -> Dict[str, Any]:
"""
Create a dict that describes an instance of this resource.
The values described with "get" should match what is possible to change with the "update" function.
If the resource does not exist then return an empty dict.
"""
result = dict(comment="", result=True, ret=None)
result["ret"] = {}
return result
async def list_(hub, ctx, **kwargs) -> Dict[str, Any]:
"""
List the resource, the "ret" value should be a dict whose keys are a unique name for each instance of the resource
The values are a description of the resource instances that matches the output of the "get" function
"""
result = dict(comment="", result=True, ret=None)
result["ret"] = {"Unique name": {"key": "value"}}
result["comment"] = f"Created '{name}'"
return result
async def create(hub, ctx, name, **kwargs) -> Dict[str, Any]:
"""
Create the named instance of this resource, assume it does not yet exist.
Any kwargs added to this function should have a default value (use None if necessary).
Additional kwargs will be used to determine how to create valid "present" states for this resource using "idem describe"
"""
result = dict(comment="", result=True, ret=None)
result["ret"] = "TODO call the create operation for this resource with **kwargs"
result["comment"] = f"Created '{name}'"
return result
async def update(hub, ctx, name, **kwargs) -> Dict[str, Any]:
"""
Update the named instance of this resource, assume it already exists.
`ctx.before` has the current status of the resource.
`kwargs` represents the desired state of the resource
Compare `ctx.before` to the "kwargs" to update the resource to the desired state.
"""
result = dict(comment="", result=True, ret=None)
result["ret"] = "TODO call the update operation for this resource with **kwargs"
result["comment"] = f"Updated '{name}'"
return result
async def delete(hub, ctx, name, **kwargs) -> Dict[str, Any]:
"""
Delete the named instance of this resource, assume it already exists
"""
result = dict(comment="", result=True, ret=None)
result["ret"] = "TODO call the delete operation for this resource with **kwargs"
result["comment"] = f"Deleted '{name}'"
return result
Soft Fail#
Implementing the “soft_fail” contract for exec or state plugins will catch any thrown errors. The error message will be injected into the “comment” of the state or exec return and the status will be set to “False”.
There can be only one “call” contract for any given function on the hub.
“soft_fail” will be overridden for this specific function if your exec module implements another call
contract.
For state modules:
__contracts__ = ["soft_fail"]
For exec modules:
__contracts__ = ["soft_fail"]
Returns#
Every exec module and state module implicitly implements the “returns” contract recursively.
This contract enforces that the return values of states and exec modules follow a specific pattern.
exec returns#
The return from all exec modules should be a dictionary with the keys “result”, “comment”, and “ret”. For example:
def exec_module(hub, ctx):
return {
# The result of the exec module operation, "True" if the exec module ran successfully, else "False"
"result": True | False,
# Any comment on the run of this exec module, such as errors or status codes
"comment": "",
# Any return value from this exec module
"ret": object(),
}
state returns#
The return from all state modules should be a dictionary with the keys “result”, “comment”, “name”, and “changes”. For example:
import dict_tools.differ as difftools
def state_module(hub, ctx, name):
# The status of the resource before the state was applied
before = {}
# The status of the resource after the state was applied
after = {}
return {
# The result of state exec module operation, "True" if the state ran successfully, else "False"
"result": True | False,
# Any comment on the run of this state module, This is used to qualify HOW the state succeeded or failed
"comment": "",
# The name that was passed as a parameter to this state module
"name": name,
"changes": difftools.deep_diff(before, after),
}
Resource#
This contract enforces “present”, “absent”, and “describe” functions in a states
plugin.
This contract implies the “describe” contract.
import dict_tools.differ as difftools
__contracts__ = ["resource"]
async def present(hub, ctx, name, **kwargs):
"""
Check if a resource exists, if it doesn't create it.
If the resource exists, make sure that it is in the state described by "kwargs"
"""
before = {}
after = {}
return {
"result": True | False,
"comment": "",
"name": name,
"changes": difftools.deep_diff(before, after),
}
async def absent(hub, ctx, name, **kwargs):
"""
Check if a resource exists, if it does, delete it.
"""
before = {}
after = {}
return {
"result": True | False,
"comment": "",
"name": name,
"changes": difftools.deep_diff(before, after),
}
async def describe(hub, ctx):
"""
Create valid present states for every instance of this resource using the given "ctx"
"""
return {
"unique_present_state_name": {
"present.function.ref": [{"present_kwarg": "present_value"}]
}
}