[Bug]: Positional-only and keyword-only parameters are mishandled
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 15.7k
- Forks
- 5.6k
- Avg merge
- 2d 44m
- Merged PRs (30d)
- 80
Description
What happened?
salt.utils.args.get_function_argspec builds a Python-2-style argspec but does not account for POSITIONAL_ONLY parameters (not a major issue since positional args are never validated). In 3006, KEYWORD_ONLY parameters are dropped as well; 3007 changed the handling of the latter in https://github.com/saltstack/salt/pull/66263, but incorrectly (see below). It reduces the impact of this issue from broad (execution module funcs with required kwarg not callable on the CLI, optional kw-only args not targetable) to specific situations (mostly subtle misbehavior, but one additional traceback) though.
In 3006: Execution module functions with required kw-only args cannot be called and optional kw-only args cannot be passed because of validation failures (unless the function accepts **kwargs). States using required kw-only args fail to be called and optional kw-only ones cannot be passed (also validation). States using required pos-only args (yeah, for whatever reason) cannot be called because of a TypeError.
In 3007: Execution module functions with required kw-only args can lead to unexpected executions by module.run/mine.update/mine.send because defaults are assigned to the wrong parameter. States using required kw-only args fail to be called because of a traceback. States using required pos-only args (for whatever reason) cannot be called because of a TypeError.
Steps to Reproduce
Sync a custom execution module arg_kinds.py:
cat > /var/cache/salt/minion/extmods/modules/arg_kinds.py <<'EOF'
def kwonly(first, *, second, third="three"):
return {"first": first, "second": second, "third": third}
def kwonly_defaults(a, b=1, *, c):
return {"a": a, "b": b, "c": c}
EOF
3006 only
Calling a function with a keyword-only parameter fails, even though the call is perfectly valid Python:
$ salt-call arg_kinds.kwonly one second=two
local:
ERROR executing 'arg_kinds.kwonly': The following keyword arguments are not valid: second=two
The same call works on 3007+.
All releases
State application cannot run states with required positional-only arguments. This is somewhat expected because of their nature, but could be handled while tackling the other issues.
3007+
3007 added param.KEYWORD_ONLY to the POSITIONAL_OR_KEYWORD branch, folding keyword-only parameter names into args. This fixes the CLI reproduction above, but breaks the invariant that defaults right-aligns against args, since keyword-only required args can follow those with defaults. This leads to consumers attributing b's default to c. For reference, see kwonly_defaults above and note that a and c are required parameters, while b defaults to 1.
$ salt-call sys.argspec arg_kinds.kwonly_defaults
local:
----------
arg_kinds.kwonly_defaults:
----------
args:
- a
- b
- c <-- this would have been absent in 3006
defaults:
- 1
kwargs:
None
varargs:
None
Now see what module.run (or mine.update/mine.send) does when the required c is omitted and it should thus fail:
$ salt-call state.single module.run foo 'arg_kinds.kwonly_defaults=[{"a": "A"}, {"b": "B"}]'
local:
----------
ID: foo
Function: module.run
Result: True
Comment: arg_kinds.kwonly_defaults: Success
Changes:
----------
arg_kinds.kwonly_defaults:
----------
a:
A
b:
B
c:
1
Similar confusion can happen in state modules:
cat > /var/cache/salt/minion/extmods/states/arg_kinds.py <<'EOF'
def kwonly(name, first, *, second, third="three"):
return {"name": name, "result": True, "comment": "", "changes": {"first": first, "second": second, "third": third}}
def kwonly_defaults(name, a, b=1, *, c):
return {"name": name, "result": True, "comment": "", "changes": {"a": a, "b": b, "c": c}}
EOF
$ salt-call state.single arg_kinds.kwonly_defaults foo a=a c=c
local:
Data failed to compile:
----------
Missing parameter b for state arg_kinds.kwonly_defaults
$ salt-call state.single arg_kinds.kwonly_defaults foo a=a b=b
local:
----------
ID: foo
Function: arg_kinds.kwonly_defaults
Result: True
Comment:
Changes:
----------
a:
a
b:
b
c:
1
and passing required keyword-only parameters to states causes a traceback, not validation failure:
$ salt-call state.single arg_kinds.kwonly foo first=one second=two
[ERROR ] An exception occurred in this state: Traceback (most recent call last):
File "/opt/saltstack/salt/lib/python3.10/site-packages/salt/state.py", line 2495, in call
ret = self.states[cdata["full"]](
File "/opt/saltstack/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 175, in __call__
ret = self.loader.run(run_func, *args, **kwargs)
File "/opt/saltstack/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1365, in run
return self._last_context.run(self._run_as, _func_or_method, *args, **kwargs)
File "/opt/saltstack/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1380, in _run_as
ret = _func_or_method(*args, **kwargs)
File "/opt/saltstack/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1416, in wrapper
return f(*args, **kwargs)
TypeError: kwonly() takes 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given
In summary, I think:
- kw-only args, both required and optional, should be supported everywhere without causing other issues.
- For consistency, it would be nice to support pos-only args for states.
Type of salt install
Official deb
Major version
3006.x, 3007.x, 3008.x
What supported OS are you seeing the problem on? Can select multiple. (If bug appears on an unsupported OS, please open a GitHub Discussion instead)
ubuntu-24.04
salt --versions-report output
Current HEAD of 3006.x and 3008.x
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with salt.utils.args.get_function_argspec and reproduce the supplied arg_kinds.py execution and state modules. Trace how module.run, mine.update, mine.send, and state calls consume argspec defaults and parameter kinds. Done means required and optional keyword-only parameters work without shifted defaults or tracebacks, with positional-only state arguments handled consistently.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- devops, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100