saltstack / saltstack/salt

[BUG] Extending a state with an empty body triggers StopIteration in state compiler

Open
#65,357 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug pending-discussion Regression State-Compiler
Dominant language
Python
Stars
15.7k
Forks
5.6k
Avg merge
2d 44m
Merged PRs (30d)
80

Description

Description
An empty state declaration (e.g. empty_declaration: {}) is legal, and is a no-op. Extending the state (via extends) with a similarly empty body triggers a StopIteration in the state compiler.

Setup
(Please provide relevant configs and/or SLS files (be sure to remove sensitive info. There is no general set-up of Salt.)

Please be as specific as possible and give set-up details.

  • on-prem machine
  • VM (Virtualbox, KVM, etc. please specify)
  • VM running on a cloud service, please be explicit and add details
  • container (Kubernetes, Docker, containerd, etc. please specify)
  • or a combination, please be explicit
  • jails if it is FreeBSD
  • classic packaging
  • onedir packaging
  • used bootstrap to install

Steps to Reproduce the behavior

extend_fail.sls:

empty_declaration: {}

extend:
  empty_declaration: {}

State application:

$ salt-call state.apply extend_fail saltenv=base
[ERROR   ] An un-handled exception was caught by Salt's global exception handler:
StopIteration: 
Traceback (most recent call last):
  File "/opt/salt/salt-call", line 23, in <module>
    sys.exit(salt_call())
  File "/opt/salt/lib/python3.10/site-packages/salt/scripts.py", line 443, in salt_call
    client.run()
  File "/opt/salt/lib/python3.10/site-packages/salt/cli/call.py", line 50, in run
    caller.run()
  File "/opt/salt/lib/python3.10/site-packages/salt/cli/caller.py", line 95, in run
    ret = self.call()
  File "/opt/salt/lib/python3.10/site-packages/salt/cli/caller.py", line 202, in call
    ret["return"] = self.minion.executors[fname](
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 159, in __call__
    ret = self.loader.run(run_func, *args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1245, in run
    return self._last_context.run(self._run_as, _func_or_method, *args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1260, in _run_as
    return _func_or_method(*args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/executors/direct_call.py", line 10, in execute
    return func(*args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 159, in __call__
    ret = self.loader.run(run_func, *args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1245, in run
    return self._last_context.run(self._run_as, _func_or_method, *args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1260, in _run_as
    return _func_or_method(*args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/modules/state.py", line 833, in apply_
    return sls(mods, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/modules/state.py", line 1479, in sls
    ret = st_.state.call_high(high_, orchestration_jid)
  File "/opt/salt/lib/python3.10/site-packages/salt/state.py", line 3493, in call_high
    high, ext_errors = self.reconcile_extend(high)
  File "/opt/salt/lib/python3.10/site-packages/salt/state.py", line 1731, in reconcile_extend
    state_type = next(x for x in body if not x.startswith("__"))
StopIteration
Traceback (most recent call last):
  File "/opt/salt/salt-call", line 23, in <module>
    sys.exit(salt_call())
  File "/opt/salt/lib/python3.10/site-packages/salt/scripts.py", line 443, in salt_call
    client.run()
  File "/opt/salt/lib/python3.10/site-packages/salt/cli/call.py", line 50, in run
    caller.run()
  File "/opt/salt/lib/python3.10/site-packages/salt/cli/caller.py", line 95, in run
    ret = self.call()
  File "/opt/salt/lib/python3.10/site-packages/salt/cli/caller.py", line 202, in call
    ret["return"] = self.minion.executors[fname](
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 159, in __call__
    ret = self.loader.run(run_func, *args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1245, in run
    return self._last_context.run(self._run_as, _func_or_method, *args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1260, in _run_as
    return _func_or_method(*args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/executors/direct_call.py", line 10, in execute
    return func(*args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 159, in __call__
    ret = self.loader.run(run_func, *args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1245, in run
    return self._last_context.run(self._run_as, _func_or_method, *args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/loader/lazy.py", line 1260, in _run_as
    return _func_or_method(*args, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/modules/state.py", line 833, in apply_
    return sls(mods, **kwargs)
  File "/opt/salt/lib/python3.10/site-packages/salt/modules/state.py", line 1479, in sls
    ret = st_.state.call_high(high_, orchestration_jid)
  File "/opt/salt/lib/python3.10/site-packages/salt/state.py", line 3493, in call_high
    high, ext_errors = self.reconcile_extend(high)
  File "/opt/salt/lib/python3.10/site-packages/salt/state.py", line 1731, in reconcile_extend
    state_type = next(x for x in body if not x.startswith("__"))
StopIteration

Expected behavior
The above example compiles cleanly, and (expectedly) does nothing, i.e.

local:

Summary for local
-----------
Succeeded: 0
Failed:   0
-----------
Total states run:    0
Total run time:  0.000 ms

Versions Report

salt --versions-report (Provided by running salt --versions-report. Please also mention any differences in master/minion versions.)
Salt Version:
          Salt: 3006.3
 
Python Version:
        Python: 3.10.13 (main, Sep  6 2023, 02:16:00) [Clang 14.0.0 (clang-1400.0.29.202)]
 
Dependency Versions:
          cffi: 1.14.6
      cherrypy: 18.6.1
      dateutil: 2.8.0
     docker-py: Not Installed
         gitdb: 4.0.5
     gitpython: 3.1.32
        Jinja2: 3.1.2
       libgit2: Not Installed
  looseversion: 1.0.2
      M2Crypto: Not Installed
          Mako: Not Installed
       msgpack: 1.0.2
  msgpack-pure: Not Installed
  mysql-python: Not Installed
     packaging: 22.0
     pycparser: 2.21
      pycrypto: Not Installed
  pycryptodome: 3.9.8
        pygit2: Not Installed
  python-gnupg: 0.4.8
        PyYAML: 6.0.1
         PyZMQ: 23.2.0
        relenv: 0.13.10
         smmap: 3.0.2
       timelib: 0.2.4
       Tornado: 4.5.3
           ZMQ: 4.3.4
 
System Versions:
          dist: darwin 22.6.0 
        locale: utf-8
       machine: x86_64
       release: 22.6.0
        system: Darwin
       version: 13.5.1 x86_64

Additional context
This issue does not present in Salt 3002.x

git blame suggests that the regression was introduced in the following commit: https://github.com/saltstack/salt/commit/8ec0c32514587869cad565cf4d58e6027c828dfa

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in salt/state.py at reconcile_extend, where the reported StopIteration occurs, and trace the call from salt/modules/state.py through state.apply. Reproduce the failure with the provided extend_fail.sls example and review commit 8ec0c32514587869cad565cf4d58e6027c828dfa. Done means the empty extension compiles cleanly and produces the expected zero-state summary.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
infrastructure
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.