Skip to content

Commit

Permalink
Support transactional systems (MicroOS) (saltstack#271)
Browse files Browse the repository at this point in the history
* Add rebootmgr module

* Add transactional_update module

* chroot: add chroot detector

* systemd: add offline mode detector

* transactional_update: add pending_transaction detector

* extra: add EFI and transactional grains

* transactional_update: add call, apply_, sls & highstate

* transactional_update: add documentation

* transactional_update: add executor

* Add changelog entry 58519.added

Closes saltstack#58519

* transactional_update: update the cleanups family

* transactional_update: add activate_transaction param

* transactional_update: skip tests on Windows
  • Loading branch information
aplanas committed Oct 5, 2020
1 parent 76c3869 commit 479ec4e
Show file tree
Hide file tree
Showing 16 changed files with 2,882 additions and 5 deletions.
1 change: 1 addition & 0 deletions changelog/58519.added
@@ -0,0 +1 @@
Add support for transactional systems, like openSUSE MicroOS
1 change: 1 addition & 0 deletions doc/ref/executors/all/index.rst
Expand Up @@ -14,3 +14,4 @@ executors modules
docker
splay
sudo
transactional_update
6 changes: 6 additions & 0 deletions doc/ref/executors/all/salt.executors.transactional_update.rst
@@ -0,0 +1,6 @@
salt.executors.transactional_update module
==========================================

.. automodule:: salt.executors.transactional_update
:members:

2 changes: 2 additions & 0 deletions doc/ref/modules/all/index.rst
Expand Up @@ -371,6 +371,7 @@ execution modules
rbac_solaris
rbenv
rdp
rebootmgr
redismod
reg
rest_pkg
Expand Down Expand Up @@ -457,6 +458,7 @@ execution modules
tls
tomcat
trafficserver
transactional_update
travisci
tuned
twilio_notify
Expand Down
5 changes: 5 additions & 0 deletions doc/ref/modules/all/salt.modules.rebootmgr.rst
@@ -0,0 +1,5 @@
salt.modules.rebootmgr module
=============================

.. automodule:: salt.modules.rebootmgr
:members:
5 changes: 5 additions & 0 deletions doc/ref/modules/all/salt.modules.transactional_update.rst
@@ -0,0 +1,5 @@
salt.modules.transactional_update module
========================================

.. automodule:: salt.modules.transactional_update
:members:
126 changes: 126 additions & 0 deletions salt/executors/transactional_update.py
@@ -0,0 +1,126 @@
"""
Transactional executor module
.. versionadded:: TBD
"""

import salt.utils.path

# Functions that are mapped into an equivalent one in
# transactional_update module
DELEGATION_MAP = {
"state.single": "transactional_update.single",
"state.sls": "transactional_update.sls",
"state.apply": "transactional_update.apply",
"state.highstate": "transactional_update.highstate",
}

# By default, all modules and functions are executed outside the
# transaction. The next two sets will enumerate the exceptions that
# will be routed to transactional_update.call()
DEFAULT_DELEGATED_MODULES = [
"ansible",
"cabal",
"chef",
"cmd",
"composer",
"cp",
"cpan",
"cyg",
"file",
"freeze",
"nix",
"npm",
"pip",
"pkg",
"puppet",
"pyenv",
"rbenv",
"scp",
]
DEFAULT_DELEGATED_FUNCTIONS = []


def __virtual__():
if salt.utils.path.which("transactional-update"):
return True
else:
return (False, "transactional_update executor requires a transactional system")


def execute(opts, data, func, args, kwargs):
"""Delegate into transactional_update module
The ``transactional_update`` module support the execution of
functions inside a transaction, as support apply a state (via
``apply``, ``sls``, ``single`` or ``highstate``).
This execution module can be used to route some Salt modules and
functions to be executed inside the transaction snapshot.
Add this executor in the minion configuration file:
.. code-block:: yaml
module_executors:
- transactional_update
- direct_call
Or use the command line parameter:
.. code-block:: bash
salt-call --module-executors='[transactional_update, direct_call]' test.version
You can also schedule a reboot if needed:
.. code-block:: bash
salt-call --module-executors='[transactional_update]' state.sls stuff activate_transaction=True
There are some configuration parameters supported:
.. code-block:: yaml
# Replace the list of default modules that all the functions
# are delegated to `transactional_update.call()`
delegated_modules: [cmd, pkg]
# Replace the list of default functions that are delegated to
# `transactional_update.call()`
delegated_functions: [pip.install]
# Expand the default list of modules
add_delegated_modules: [ansible]
# Expand the default list of functions
add_delegated_functions: [file.copy]
"""
fun = data["fun"]
module, _ = fun.split(".")

delegated_modules = set(opts.get("delegated_modules", DEFAULT_DELEGATED_MODULES))
delegated_functions = set(
opts.get("delegated_functions", DEFAULT_DELEGATED_FUNCTIONS)
)
if "executor_opts" in data:
delegated_modules |= set(data["executor_opts"].get("add_delegated_modules", []))
delegated_functions |= set(
data["executor_opts"].get("add_delegated_functions", [])
)
else:
delegated_modules |= set(opts.get("add_delegated_modules", []))
delegated_functions |= set(opts.get("add_delegated_functions", []))

if fun in DELEGATION_MAP:
result = __executors__["direct_call.execute"](
opts, data, __salt__[DELEGATION_MAP[fun]], args, kwargs
)
elif module in delegated_modules or fun in delegated_functions:
result = __salt__["transactional_update.call"](fun, *args, **kwargs)
else:
result = __executors__["direct_call.execute"](opts, data, func, args, kwargs)

return result
29 changes: 29 additions & 0 deletions salt/grains/extra.py
Expand Up @@ -3,14 +3,18 @@
from __future__ import absolute_import, print_function, unicode_literals

# Import python libs
import glob
import logging
import os

# Import third party libs
import logging

# Import salt libs
import salt.utils
import salt.utils.data
import salt.utils.files
import salt.utils.path
import salt.utils.platform
import salt.utils.yaml

Expand Down Expand Up @@ -83,3 +87,28 @@ def suse_backported_capabilities():
'__suse_reserved_pkg_patches_support': True,
'__suse_reserved_saltutil_states_support': True
}


def __secure_boot():
"""Detect if secure-boot is enabled."""
enabled = False
sboot = glob.glob("/sys/firmware/efi/vars/SecureBoot-*/data")
if len(sboot) == 1:
with salt.utils.files.fopen(sboot[0], "rb") as fd:
enabled = fd.read()[-1:] == b"\x01"
return enabled


def uefi():
"""Populate UEFI grains."""
grains = {
"efi": os.path.exists("/sys/firmware/efi/systab"),
"efi-secure-boot": __secure_boot(),
}

return grains


def transactional():
"""Determine if the system in transactional."""
return {"transactional": bool(salt.utils.path.which("transactional-update"))}
39 changes: 36 additions & 3 deletions salt/modules/chroot.py
Expand Up @@ -21,6 +21,7 @@
import salt.exceptions
import salt.ext.six as six
import salt.utils.args
import salt.utils.files


__func_alias__ = {
Expand Down Expand Up @@ -82,6 +83,38 @@ def create(root):
return True


def in_chroot():
"""
Return True if the process is inside a chroot jail
.. versionadded:: TBD
CLI Example:
.. code-block:: bash
salt myminion chroot.in_chroot
"""
result = False

try:
# We cannot assume that we are "root", so we cannot read
# '/proc/1/root', that is required for the usual way of
# detecting that we are in a chroot jail. We use the debian
# ischroot method.
with salt.utils.files.fopen(
"/proc/1/mountinfo"
) as root_fd, salt.utils.files.fopen("/proc/self/mountinfo") as self_fd:
root_mountinfo = root_fd.read()
self_mountinfo = self_fd.read()
result = root_mountinfo != self_mountinfo
except OSError:
pass

return result


def call(root, function, *args, **kwargs):
'''
Executes a Salt function inside a chroot environment.
Expand Down Expand Up @@ -121,7 +154,7 @@ def call(root, function, *args, **kwargs):
so_mods=__salt__['config.option']('thin_so_mods', '')
)
# Some bug in Salt is preventing us to use `archive.tar` here. A
# AsyncZeroMQReqChannel is not closed at the end os the salt-call,
# AsyncZeroMQReqChannel is not closed at the end of the salt-call,
# and makes the client never exit.
#
# stdout = __salt__['archive.tar']('xzf', thin_path, dest=thin_dest_path)
Expand Down Expand Up @@ -198,7 +231,7 @@ def apply_(root, mods=None, **kwargs):

def _create_and_execute_salt_state(root, chunks, file_refs, test, hash_type):
'''
Create the salt_stage tarball, and execute in the chroot
Create the salt_state tarball, and execute in the chroot
'''
# Create the tar containing the state pkg and relevant files.
salt.client.ssh.wrapper.state._cleanup_slsmod_low_data(chunks)
Expand All @@ -210,7 +243,7 @@ def _create_and_execute_salt_state(root, chunks, file_refs, test, hash_type):
ret = None

# Create a temporary directory inside the chroot where we can move
# the salt_stage.tgz
# the salt_state.tgz
salt_state_path = tempfile.mkdtemp(dir=root)
salt_state_path = os.path.join(salt_state_path, 'salt_state.tgz')
salt_state_path_in_chroot = salt_state_path.replace(root, '', 1)
Expand Down

0 comments on commit 479ec4e

Please sign in to comment.