agents/COPILOT.md
This is the Salt project - a Python-based configuration management and remote execution system using master-minion architecture with a dynamic loader system for plugins.
CRITICAL: Write code as a CPython core developer:
Zen of Python: Readability counts. Explicit > implicit. Simple > complex. Errors never pass silently.
TDD for Bug Fixes: Write failing test → verify fail → fix bug → verify pass → run related tests
CRITICAL: NO attribution lines
#NNNNSalt components:
salt/modules/): CLI commands (264+)salt/states/): Declarative configuration (126+)salt/utils/): Shared utilities (170+)See agents/docs/architecture.md for complete details.
import logging
import salt.exceptions
import salt.utils.platform
log = logging.getLogger(__name__)
def __virtual__():
"""Return True to load, False or (False, reason) to skip"""
if not salt.utils.path.which("required_binary"):
return False, "required_binary not found"
return True
def function_name(param1, param2=None):
"""
CLI Example::
salt '*' module.function_name value
"""
if not param1:
raise salt.exceptions.SaltInvocationError("param1 required")
return __salt__["cmd.run"](f"command {param1}")
def managed(name, value=None):
ret = {"name": name, "result": False, "changes": {}, "comment": ""}
# Validate → Check current → If correct return → If test mode return None → Apply → Return
if not name:
ret["comment"] = "Name required"
return ret
current = __salt__["module.get"](name)
if current == value:
ret["result"] = True
ret["comment"] = "Already correct"
return ret
if __opts__["test"]:
ret["result"] = None
ret["changes"] = {"old": current, "new": value}
ret["comment"] = "Would change"
return ret
try:
__salt__["module.set"](name, value)
ret["result"] = True
ret["changes"] = {"old": current, "new": value}
ret["comment"] = "Updated"
except Exception as exc:
ret["comment"] = str(exc)
return ret
See agents/docs/module-templates.md for complete templates.
Salt injects these into module scope:
__opts__: Config - __opts__["test"], __opts__.get("id")__grains__: System data - __grains__.get("os_family")__pillar__: Secure data - __pillar__.get("password")__context__: Per-run cache for expensive operations__salt__: Execution modules - __salt__["pkg.install"]("nginx")__utils__: Utilities - __utils__["files.is_text"](path)__states__: State modules (states only)Context caching pattern:
if "pkg_list" not in __context__:
__context__["pkg_list"] = expensive_fetch()
return __context__["pkg_list"]
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.
Every function requires description, parameters, and CLI example:
def my_function(name, value=None):
"""
Description of functionality
name
Description of name parameter
CLI Example::
salt '*' module.my_function foo value=bar
"""
from salt.exceptions import (
CommandExecutionError, # Operation failures
SaltInvocationError, # Invalid arguments
CommandNotFoundError, # Missing binaries
)
if not required_param:
raise SaltInvocationError("required_param must be provided")
Declare platform/dependency requirements:
# Platform check
def __virtual__():
if salt.utils.platform.is_windows():
return False, "Not available on Windows"
return True
# Virtual name (for interface modules)
__virtualname__ = "pkg"
def __virtual__():
if __grains__.get("os_family") == "Debian":
return __virtualname__
return False
See agents/docs/module-templates.md for all patterns.
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/pytest
See agents/docs/development-setup.md for complete setup.
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
See agents/docs/testing.md for complete guide.
salt.utils.platform.is_windows()
salt.utils.platform.is_linux()
__grains__.get("os_family") # "Debian", "RedHat", etc.
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)
__salt__["file.file_exists"](path)
log = logging.getLogger(__name__)__virtual__() if platform-specificSaltInvocationError)CommandExecutionError)Essential guides in agents/docs/:
Key example files: salt/modules/test.py, salt/modules/file.py, salt/states/pkg.py
__context__