reintroduce Salt
Some checks failed
Lint / test (push) Waiting to run
CI / test (push) Has been cancelled

This commit is contained in:
Miguel Jacq 2026-06-18 20:35:38 +10:00
parent 0d111caf62
commit adfeb21d4b
Signed by: mig5
GPG key ID: 03906B4110AAD3B8
4 changed files with 67 additions and 19 deletions

View file

@ -5,9 +5,10 @@
* Detect active sysctl parameters and write them to a `/etc/sysctl.d/99-enroll.conf` file
* Use `no_log` on systemd unit interrogations to suppress potential sensitive output when applying Ansible
* Support manifesting Puppet code, as well as Ansible!
* Support manifesting Salt code, as well as Ansible and Puppet!
* A lot of under-the-bonnet refactoring to make it easier to extend to cover other config managers (that don't suck) in future.
* Support for detecting Docker images
* Add support for detecting flatpaks and snaps (manifests Ansible code only, not Puppet at this time)
* Support for detecting Docker images. You will need to install puppetlabs-docker module if you're using the Puppet manifester.
* Add support for detecting flatpaks and snaps (manifests Ansible code only, not Puppet or Salt at this time)
# 0.6.0

View file

@ -488,7 +488,13 @@ cd /tmp/enroll-salt
sudo salt-call --local --file-root ./states --pillar-root ./pillar --id host.example.com state.apply test=True
```
Re-running Salt `--fqdn` output into the same directory adds or replaces that minion's top/pillar data without deleting other generated minions. Docker images with registry digests are rendered with Salt's native `docker_image.present` state. Podman images with registry digests are rendered as guarded `podman pull` / `podman tag` command states. Images without `RepoDigest` are recorded in harvest state and notes, but are not converted into exact pull states. Flatpak, Snap, and live firewall runtime snapshots are listed as notes in the generated Salt README rather than converted into Salt states.
Re-running Salt `--fqdn` output into the same directory adds or replaces that minion's top/pillar data without deleting other generated minions.
Docker and Podman images with registry digests are rendered as guarded `cmd.run` states that use the local `docker`/`podman` CLI directly (`pull`, `image inspect`, and `tag`).
This is because Salt Stack, in 3008, does not have proper Docker extensions that actually work. Wow.
Certain other things, like in Puppet, are not 'manifested' into Salt states unlike Ansible, at this time: these are Flatpak, Snap, and live firewall rules.
### Manifest with `--sops`
```bash

View file

@ -119,7 +119,9 @@ class SaltRole(CMModule):
for alias in item["tag_aliases"]:
alias_ref = str(alias.get("ref") or "")
alias["tag_cmd"] = _container_tag_cmd(engine, pull_ref, alias_ref)
alias["tag_unless"] = _container_exists_cmd(engine, alias_ref)
alias["tag_unless"] = _container_tag_matches_cmd(
engine, pull_ref, alias_ref
)
self.container_images.append(item)
for note in snap.get("notes", []) or []:
self.notes.append(str(note))
@ -239,6 +241,34 @@ def _container_exists_cmd(engine: str, ref: str) -> str:
return f"docker image inspect {_shell_quote(ref)} >/dev/null 2>&1"
def _container_image_id_expr(engine: str, ref: str) -> str:
"""Return a shell expression that extracts an inspected image ID.
Salt renders SLS files through Jinja before YAML, so Docker's normal
format template cannot be emitted literally without careful escaping. Use
JSON output plus sed instead; it avoids Go-template braces in generated
Salt states and pillar data.
"""
sed_id = (
r"sed -n 's/^[[:space:]]*\"Id\":[[:space:]]*\"\([^\"]*\)\".*/\1/p' "
r"| head -n 1"
)
return (
f"{_shell_quote(engine)} image inspect {_shell_quote(ref)} "
f"2>/dev/null | {sed_id}"
)
def _container_tag_matches_cmd(engine: str, pull_ref: str, tag_ref: str) -> str:
"""Return a shell guard that is true only when tag_ref points at pull_ref."""
return (
f'test "$({_container_image_id_expr(engine, tag_ref)})" '
f'= "$({_container_image_id_expr(engine, pull_ref)})"'
)
def _container_tag_cmd(engine: str, pull_ref: str, tag_ref: str) -> str:
return f"{engine} tag {_shell_quote(pull_ref)} {_shell_quote(tag_ref)}"
@ -581,13 +611,13 @@ def _render_static_role(srole: SaltRole) -> str:
if not engine or not pull_ref:
continue
if engine == "docker":
pull_state_id = _state_id("docker_image", pull_ref, role=srole.module_name)
pull_state_id = _state_id("docker_pull", pull_ref, role=srole.module_name)
lines.extend(
[
f"{pull_state_id}:",
" docker_image.present:",
f" - name: {_yaml_quote(pull_ref)}",
" - force: false",
" cmd.run:",
f" - name: {_yaml_quote(image.get('pull_cmd') or _container_pull_cmd(engine, pull_ref))}",
f" - unless: {_yaml_quote(image.get('pull_unless') or _container_exists_cmd(engine, pull_ref))}",
"",
]
)
@ -600,9 +630,9 @@ def _render_static_role(srole: SaltRole) -> str:
f"{_state_id('docker_tag', tag_ref, role=srole.module_name)}:",
" cmd.run:",
f" - name: {_yaml_quote(alias.get('tag_cmd') or _container_tag_cmd(engine, pull_ref, tag_ref))}",
f" - unless: {_yaml_quote(alias.get('tag_unless') or _container_exists_cmd(engine, tag_ref))}",
f" - unless: {_yaml_quote(alias.get('tag_unless') or _container_tag_matches_cmd(engine, pull_ref, tag_ref))}",
" - require:",
f" - docker_image: {pull_state_id}",
f" - cmd: {pull_state_id}",
"",
]
)
@ -815,10 +845,10 @@ def _render_pillar_role(srole: SaltRole) -> str:
"",
"{% for image in role.get('container_images', []) %}",
"{% if image.get('engine') == 'docker' and image.get('pull_ref') %}",
f"enroll_docker_image_{role_key}_{{{{ loop.index }}}}:",
" docker_image.present:",
" - name: {{ image.get('pull_ref')|yaml_dquote }}",
" - force: false",
f"enroll_docker_pull_{role_key}_{{{{ loop.index }}}}:",
" cmd.run:",
" - name: {{ image.get('pull_cmd')|yaml_dquote }}",
" - unless: {{ image.get('pull_unless')|yaml_dquote }}",
"{% set image_loop = loop.index %}",
"{% for alias in image.get('tag_aliases', []) %}",
f"enroll_docker_tag_{role_key}_{{{{ image_loop }}}}_{{{{ loop.index }}}}:",
@ -826,7 +856,7 @@ def _render_pillar_role(srole: SaltRole) -> str:
" - name: {{ alias.get('tag_cmd')|yaml_dquote }}",
" - unless: {{ alias.get('tag_unless')|yaml_dquote }}",
" - require:",
f" - docker_image: enroll_docker_image_{role_key}_{{{{ image_loop }}}}",
f" - cmd: enroll_docker_pull_{role_key}_{{{{ image_loop }}}}",
"{% endfor %}",
"{% elif image.get('engine') == 'podman' and image.get('pull_ref') %}",
f"enroll_podman_pull_{role_key}_{{{{ loop.index }}}}:",
@ -1033,7 +1063,7 @@ This Salt target reuses the existing harvest state without changing harvesting b
- Managed directories, files, and symlinks from harvested roles.
- Basic service enablement/running-state resources.
- `/etc/sysctl.d/99-enroll.conf` plus an `onchanges` sysctl apply command when present.
- Docker images by digest using Salt's native `docker_image.present` state.
- Docker images by digest using guarded `docker pull` / `docker tag` command states.
- Podman images by digest using guarded `podman pull` / `podman tag` command states.
## Current limitations

View file

@ -430,9 +430,14 @@ def test_manifest_salt_renders_container_images_in_single_and_fqdn_modes(
sls = (out / "states" / "roles" / "container_images" / "init.sls").read_text(
encoding="utf-8"
)
assert "docker_image.present:" in sls
assert "docker_image.present:" not in sls
assert "docker pull" in sls
assert digest in sls
assert "docker image inspect" in sls
assert "{{.Id}}" not in sls
assert "sed -n" in sls
assert "docker tag" in sls
assert "- cmd: enroll_docker_pull_container_images" in sls
assert "podman pull" in sls
assert "podman tag" in sls
@ -451,7 +456,13 @@ def test_manifest_salt_renders_container_images_in_single_and_fqdn_modes(
fqdn_sls = (
fqdn_out / "states" / "roles" / "container_images" / "init.sls"
).read_text(encoding="utf-8")
assert "docker_image.present:" in fqdn_sls
assert "docker_image.present:" not in fqdn_sls
assert "enroll_docker_pull_container_images" in fqdn_sls
assert "enroll_podman_pull_container_images" in fqdn_sls
assert "image.get('pull_cmd')" in fqdn_sls
assert "podman pull" in pillar_path.with_suffix(".sls").read_text(encoding="utf-8")
pillar_text = pillar_path.with_suffix(".sls").read_text(encoding="utf-8")
assert "docker pull" in pillar_text
assert "docker image inspect" in pillar_text
assert "{{.Id}}" not in pillar_text
assert "sed -n" in pillar_text
assert "podman pull" in pillar_text