agents/CURSOR.md
You are working on Salt, a Python-based configuration management and remote execution system. Follow these rules when writing or modifying Salt code.
CODING PHILOSOPHY:
GIT WORKFLOW:
#NNNNLINTING:
black .)isort .)nox -e lint-saltSalt Components:
salt/modules/) - 264+salt/states/) - 126+salt/utils/) - 170+See agents/docs/architecture.md for complete architecture.
import logging
import salt.exceptions
import salt.utils.platform
log = logging.getLogger(__name__)
def __virtual__():
"""Returns True/False/(False, reason)"""
if not salt.utils.path.which("required_cmd"):
return False, "required_cmd not found"
return True
def my_function(name, param=None):
"""
CLI Example::
salt '*' mymodule.my_function foo param=bar
"""
if not name:
raise salt.exceptions.SaltInvocationError("name required")
return __salt__["cmd.run"](f"command {name}")
ret = {
"name": name, # Resource name
"result": False, # True/False/None (test mode)
"changes": {}, # {"old": ..., "new": ...}
"comment": "" # What happened
}
State flow: Validate → Check current → If correct return → If test mode return None → Make changes → Return result
Complete templates: agents/docs/module-templates.md
__opts__: Config (__opts__["test"], __opts__.get("id"))__grains__: System info (__grains__.get("os_family"))__pillar__: Secure data (__pillar__.get("password"))__context__: Per-run cache - use for expensive operations__salt__: Execution modules (__salt__["pkg.install"]("nginx"))__utils__: Utilities (__utils__["files.is_text"](path))__states__: State modules (states only)Context cache pattern:
if "cache_key" not in __context__:
__context__["cache_key"] = expensive_operation()
return __context__["cache_key"]
from salt.exceptions import (
CommandExecutionError, # Operation failed
SaltInvocationError, # Bad arguments
CommandNotFoundError, # Binary not found
)
log = logging.getLogger(__name__)
log.debug("Processing %s", filename) # Good - lazy formatting
log.debug(f"Processing {filename}") # Bad - f-strings evaluated early
Never log secrets.
salt.utils.platform.is_windows()
salt.utils.platform.is_linux()
__grains__.get("os_family") # "Debian", "RedHat", etc.
from salt.utils.decorators.path import which
from salt.utils.decorators import depends, memoize
@which("systemctl") # Require binary
@depends("docker") # Require Python module
@memoize # Cache forever
Two environments required:
# venv310
python3.10 -m venv venv310 && source venv310/bin/activate
pip install --upgrade pip setuptools wheel
pip install -r requirements/static/pkg/py3.10/linux.txt # or darwin.txt/windows.txt
pip install -r requirements/pytest.txt -r requirements/static/ci/py3.10/tools.txt
pip install pre-commit python-tools-scripts && pip install -e . && deactivate
# venv311
python3.11 -m venv venv311 && source venv311/bin/activate
pip install --upgrade pip setuptools wheel
pip install -r requirements/static/pkg/py3.11/linux.txt # or darwin.txt/windows.txt
pip install -r requirements/pytest.txt -r requirements/static/ci/py3.11/tools.txt
pip install pre-commit python-tools-scripts && pip install -e . && pre-commit install && deactivate
Always use full paths: ./venv310/bin/python, ./venv310/bin/pytest
Complete setup: agents/docs/development-setup.md
import pytest
import salt.modules.mymodule as mymodule
@pytest.fixture
def configure_loader_modules():
return {
mymodule: {
"__opts__": {"test": False},
"__grains__": {"os": "Linux"},
"__salt__": {},
}
}
def test_function():
result = mymodule.my_function("test")
assert result == "expected"
# Nox
nox -e test-3 -- tests/pytests/unit/test_loader.py
nox -e test-3 -- --lf # Last failed
# Direct (faster)
./venv310/bin/pytest tests/pytests/unit/test_foo.py -v
Complete guide: agents/docs/testing.md
log = logging.getLogger(__name__)__virtual__() if platform-specificSaltInvocationError)CommandExecutionError)import logging
import salt.exceptions
import salt.utils.args
import salt.utils.platform
import salt.utils.path
log = logging.getLogger(__name__)
output = __salt__["cmd.run"]("ls -la")
result = __salt__["cmd.run_all"]("cmd") # {"retcode": 0, "stdout": "...", "stderr": "..."}
content = __salt__["file.read"](path)
__salt__["file.write"](path, content)
exists = __salt__["file.file_exists"](path)
Key example files: salt/modules/test.py, salt/modules/file.py, salt/states/pkg.py
__context__