Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accumulated changes required for Yomi #165

Merged
merged 9 commits into from
Jul 30, 2019
36 changes: 20 additions & 16 deletions salt/modules/chroot.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,25 @@ def __virtual__():
return (False, 'Module chroot requires the command chroot')


def exist(name):
def exist(root):
'''
aplanas marked this conversation as resolved.
Show resolved Hide resolved
Return True if the chroot environment is present.
'''
dev = os.path.join(name, 'dev')
proc = os.path.join(name, 'proc')
return all(os.path.isdir(i) for i in (name, dev, proc))
dev = os.path.join(root, 'dev')
proc = os.path.join(root, 'proc')
sys = os.path.join(root, 'sys')
return all(os.path.isdir(i) for i in (root, dev, proc, sys))


def create(name):
def create(root):
'''
Create a basic chroot environment.

Note that this environment is not functional. The caller needs to
install the minimal required binaries, including Python if
chroot.call is called.

name
root
Path to the chroot environment

CLI Example:
Expand All @@ -77,26 +78,28 @@ def create(name):
salt myminion chroot.create /chroot

'''
if not exist(name):
dev = os.path.join(name, 'dev')
proc = os.path.join(name, 'proc')
if not exist(root):
dev = os.path.join(root, 'dev')
proc = os.path.join(root, 'proc')
sys = os.path.join(root, 'sys')
try:
os.makedirs(dev, mode=0o755)
os.makedirs(proc, mode=0o555)
os.makedirs(sys, mode=0o555)
except OSError as e:
log.error('Error when trying to create chroot directories: %s', e)
return False
return True


def call(name, function, *args, **kwargs):
def call(root, function, *args, **kwargs):
'''
Executes a Salt function inside a chroot environment.

The chroot does not need to have Salt installed, but Python is
required.

name
root
Path to the chroot environment

function
Expand All @@ -107,18 +110,19 @@ def call(name, function, *args, **kwargs):
.. code-block:: bash

salt myminion chroot.call /chroot test.ping
salt myminion chroot.call /chroot ssh.set_auth_key user key=mykey

'''

if not function:
raise CommandExecutionError('Missing function parameter')

if not exist(name):
if not exist(root):
raise CommandExecutionError('Chroot environment not found')

# Create a temporary directory inside the chroot where we can
# untar salt-thin
thin_dest_path = tempfile.mkdtemp(dir=name)
thin_dest_path = tempfile.mkdtemp(dir=root)
thin_path = __utils__['thin.gen_thin'](
__opts__['cachedir'],
extra_mods=__salt__['config.option']('thin_extra_mods', ''),
Expand All @@ -130,7 +134,7 @@ def call(name, function, *args, **kwargs):
return {'result': False, 'comment': stdout}

chroot_path = os.path.join(os.path.sep,
os.path.relpath(thin_dest_path, name))
os.path.relpath(thin_dest_path, root))
try:
safe_kwargs = clean_kwargs(**kwargs)
salt_argv = [
Expand All @@ -144,8 +148,8 @@ def call(name, function, *args, **kwargs):
'-l', 'quiet',
'--',
function
] + list(args) + ['{}={}'.format(k, v) for (k, v) in safe_kwargs]
ret = __salt__['cmd.run_chroot'](name, [str(x) for x in salt_argv])
] + list(args) + ['{}={}'.format(k, v) for (k, v) in safe_kwargs.items()]
ret = __salt__['cmd.run_chroot'](root, [str(x) for x in salt_argv])
if ret['retcode'] != EX_OK:
raise CommandExecutionError(ret['stderr'])

Expand Down
12 changes: 9 additions & 3 deletions salt/modules/cmdmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -3064,13 +3064,19 @@ def run_chroot(root,

if isinstance(cmd, (list, tuple)):
cmd = ' '.join([six.text_type(i) for i in cmd])
cmd = 'chroot {0} {1} -c {2}'.format(root, sh_, _cmd_quote(cmd))

# If runas and group are provided, we expect that the user lives
# inside the chroot, not outside.
if runas:
userspec = '--userspec {}:{}'.format(runas, group if group else '')
else:
userspec = ''

cmd = 'chroot {} {} {} -c {}'.format(userspec, root, sh_, _cmd_quote(cmd))

run_func = __context__.pop('cmd.run_chroot.func', run_all)

ret = run_func(cmd,
runas=runas,
group=group,
cwd=cwd,
stdin=stdin,
shell=shell,
Expand Down
20 changes: 14 additions & 6 deletions salt/modules/freezer.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def freeze(name=None, force=False, **kwargs):
states_path = _states_path()

try:
os.makedirs(states_path)
os.makedirs(states_path, exist_ok=True)
except OSError as e:
msg = 'Error when trying to create the freezer storage %s: %s'
log.error(msg, states_path, e)
Expand All @@ -163,13 +163,13 @@ def freeze(name=None, force=False, **kwargs):
safe_kwargs = clean_kwargs(**kwargs)
pkgs = __salt__['pkg.list_pkgs'](**safe_kwargs)
repos = __salt__['pkg.list_repos'](**safe_kwargs)
for name, content in zip(_paths(name), (pkgs, repos)):
with fopen(name, 'w') as fp:
for fname, content in zip(_paths(name), (pkgs, repos)):
with fopen(fname, 'w') as fp:
json.dump(content, fp)
return True


def restore(name=None, **kwargs):
def restore(name=None, clean=False, **kwargs):
'''
Make sure that the system contains the packages and repos from a
frozen state.
Expand All @@ -190,6 +190,9 @@ def restore(name=None, **kwargs):
name
Name of the frozen state. Optional.

clean
In True remove the frozen information YAML from the cache

CLI Example:

.. code-block:: bash
Expand All @@ -203,8 +206,8 @@ def restore(name=None, **kwargs):

frozen_pkgs = {}
frozen_repos = {}
for name, content in zip(_paths(name), (frozen_pkgs, frozen_repos)):
with fopen(name) as fp:
for fname, content in zip(_paths(name), (frozen_pkgs, frozen_repos)):
with fopen(fname) as fp:
content.update(json.load(fp))

# The ordering of removing or adding packages and repos can be
Expand Down Expand Up @@ -291,4 +294,9 @@ def restore(name=None, **kwargs):
log.error(msg, repo, e)
res['comment'].append(msg % (repo, e))

# Clean the cached YAML files
if clean and not res['comment']:
for fname in _paths(name):
os.remove(fname)

return res
13 changes: 10 additions & 3 deletions salt/modules/zypperpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,14 @@ def _clean_cache():
'''
Clean cached results
'''
keys = []
for cache_name in ['pkg.list_pkgs', 'pkg.list_provides']:
__context__.pop(cache_name, None)
for contextkey in __context__:
if contextkey.startswith(cache_name):
keys.append(contextkey)

for key in keys:
__context__.pop(key, None)


def list_upgrades(refresh=True, root=None, **kwargs):
Expand Down Expand Up @@ -809,9 +815,10 @@ def list_pkgs(versions_as_list=False, root=None, includes=None, **kwargs):

includes = includes if includes else []

contextkey = 'pkg.list_pkgs'
# Results can be different if a different root or a different
# inclusion types are passed
contextkey = 'pkg.list_pkgs_{}_{}'.format(root, includes)

# TODO(aplanas): this cached value depends on the parameters
if contextkey not in __context__:
ret = {}
cmd = ['rpm']
Expand Down
36 changes: 34 additions & 2 deletions tests/unit/modules/test_chroot.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ def test_exist(self, isdir):
'''
Test if the chroot environment exist.
'''
isdir.side_effect = (True, True, True)
isdir.side_effect = (True, True, True, True)
self.assertTrue(chroot.exist('/chroot'))

isdir.side_effect = (True, True, False)
isdir.side_effect = (True, True, True, False)
self.assertFalse(chroot.exist('/chroot'))

@patch('os.makedirs')
Expand Down Expand Up @@ -182,3 +182,35 @@ def test_call_success(self, mkdtemp, exist):
salt_mock['archive.tar'].assert_called_once()
salt_mock['cmd.run_chroot'].assert_called_once()
utils_mock['files.rm_rf'].assert_called_once()

@patch('salt.modules.chroot.exist')
@patch('tempfile.mkdtemp')
def test_call_success_parameters(self, mkdtemp, exist):
'''
Test execution of Salt functions in chroot with parameters.
'''
# Success test
exist.return_value = True
mkdtemp.return_value = '/chroot/tmp01'
utils_mock = {
'thin.gen_thin': MagicMock(return_value='/salt-thin.tgz'),
'files.rm_rf': MagicMock(),
'json.find_json': MagicMock(return_value={'return': 'result'})
}
salt_mock = {
'archive.tar': MagicMock(return_value=''),
'config.option': MagicMock(),
'cmd.run_chroot': MagicMock(return_value={
'retcode': 0,
'stdout': '',
}),
}
with patch.dict(chroot.__utils__, utils_mock), \
patch.dict(chroot.__salt__, salt_mock):
self.assertEqual(chroot.call('/chroot', 'ssh.set_auth_key',
user='user', key='key'), 'result')
utils_mock['thin.gen_thin'].assert_called_once()
salt_mock['config.option'].assert_called()
salt_mock['archive.tar'].assert_called_once()
salt_mock['cmd.run_chroot'].assert_called_once()
utils_mock['files.rm_rf'].assert_called_once()
50 changes: 50 additions & 0 deletions tests/unit/modules/test_cmdmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,22 @@ def test_run_cwd_doesnt_exist_issue_7154(self):
else:
raise RuntimeError

@skipIf(salt.utils.platform.is_windows(), 'Do not run on Windows')
@skipIf(salt.utils.platform.is_darwin(), 'Do not run on MacOS')
def test_run_cwd_in_combination_with_runas(self):
'''
cmd.run executes command in the cwd directory
when the runas parameter is specified
'''
cmd = 'pwd'
cwd = '/tmp'
runas = os.getlogin()

with patch.dict(cmdmod.__grains__, {'os': 'Darwin',
'os_family': 'Solaris'}):
stdout = cmdmod._run(cmd, cwd=cwd, runas=runas).get('stdout')
self.assertEqual(stdout, cwd)

def test_run_all_binary_replace(self):
'''
Test for failed decoding of binary data, for instance when doing
Expand Down Expand Up @@ -401,3 +417,37 @@ def test_run_all_output_encoding(self):
ret = cmdmod.run_all('some command', output_encoding='latin1')

self.assertEqual(ret['stdout'], stdout)

def test_run_chroot_runas(self):
'''
Test run_chroot when a runas parameter is provided
'''
with patch.dict(cmdmod.__salt__, {'mount.mount': MagicMock(),
'mount.umount': MagicMock()}):
with patch('salt.modules.cmdmod.run_all') as run_all_mock:
cmdmod.run_chroot('/mnt', 'ls', runas='foobar')
run_all_mock.assert_called_with(
'chroot --userspec foobar: /mnt /bin/sh -c ls',
bg=False,
clean_env=False,
cwd=None,
env=None,
ignore_retcode=False,
log_callback=None,
output_encoding=None,
output_loglevel='quiet',
pillar=None,
pillarenv=None,
python_shell=True,
reset_system_locale=True,
rstrip=True,
saltenv='base',
shell='/bin/bash',
stdin=None,
success_retcodes=None,
success_stderr=None,
success_stdout=None,
template=None,
timeout=None,
umask=None,
use_vt=False)
Loading