add --pre-flight ssh option

This commit is contained in:
ch3ll 2020-04-06 13:21:02 -04:00
parent 4fa68c9dce
commit 0c365c19a4
No known key found for this signature in database
GPG key ID: 1124C6796EBDBD8D
12 changed files with 134 additions and 39 deletions

View file

@ -506,6 +506,12 @@
# Boolean to run command via sudo.
#ssh_sudo: False
# Boolean to run ssh_pre_flight script defined in roster. By default
# the script will only run if the thin_dir does not exist on the targeted
# minion. This forces the script to run regardless of the thin dir existing
# or not.
#ssh_run_pre_flight: True
# Number of seconds to wait for a response when establishing an SSH connection.
#ssh_timeout: 60

View file

@ -105,6 +105,14 @@ Options
Pass a JID to be used instead of generating one.
.. option:: --pre-flight
Run the ssh_pre_flight script defined in the roster.
By default this script will only run if the thin dir
does not exist on the target minion. This option will
force the script to run regardless of the thin dir
existing or not.
Authentication Options
----------------------

View file

@ -1341,6 +1341,15 @@ salt-ssh.
groupA: minion1,minion2
groupB: minion1,minion3
.. conf_master:: ssh_run_pre_flight
Default: False
Run the ssh_pre_flight script defined in the salt-ssh roster. Be default
the script will only run when the thin dir does not exist on the targeted
minion. This will force the script to run and not check if the thin dir
exists first.
.. conf_master:: thin_extra_mods
``thin_extra_mods``

View file

@ -43,4 +43,7 @@ run this script.
ssh_pre_flight: /srv/salt/pre_flight.sh
The `ssh_pre_flight` script will only run if the thin dir is not currently on the
minion.
minion. If you want to force the script to run you have the following options:
- Wipe the thin dir on the targeted minion using the -w arg.
- Set ssh_run_pre_flight to True in the config.
- Run salt-ssh with the --pre-flight arg.

View file

@ -63,7 +63,9 @@ The information which can be stored in a roster ``target`` is the following:
# octal (so for 0o077 in YAML you would do 0077, or 63)
ssh_pre_flight: # Path to a script that will run before all other salt-ssh
# commands. Will only run the first time when the thin dir
# does not exist. Added in Sodium Release.
# does not exist, unless --pre-flight is passed to salt-ssh
# command or ssh_run_pre_flight is set to true in the config
# Added in Sodium Release.
.. _ssh_pre_flight:
@ -74,7 +76,11 @@ A Salt-SSH roster option `ssh_pre_flight` was added in the Sodium release. This
you to run a script before Salt-SSH tries to run any commands. You can set this option
in the roster for a specific minion or use the `roster_defaults` to set it for all minions.
This script will only run if the thin dir is not currently on the minion. This means it will
only run on the first run of salt-ssh or if you have recently wiped out your thin dir.
only run on the first run of salt-ssh or if you have recently wiped out your thin dir. If
you want to intentionally run the script again you have a couple of options:
- Wipe out your thin dir by using the -w salt-ssh arg.
- Set ssh_run_pre_flight to True in the config
- Run salt-ssh with the --pre-flight arg.
.. _roster_defaults:

View file

@ -991,6 +991,15 @@ class Single(object):
return self.execute_script(script)
def check_thin_dir(self):
'''
check if the thindir exists on the remote machine
'''
stdout, stderr, retcode = self.shell.exec_cmd('test -d {0}'.format(self.thin_dir))
if retcode != 0:
return False
return True
def deploy(self):
"""
Deploy salt-thin
@ -1026,8 +1035,8 @@ class Single(object):
stdout = stderr = retcode = None
if self.ssh_pre_flight:
if os.path.exists(self.thin_dir):
log.debug('{0} thin dir already exists. Not running ssh_pre_flight script'.format(self.thin_dir))
if self.check_thin_dir() and not self.opts.get('ssh_run_pre_flight', False):
log.info('{0} thin dir already exists. Not running ssh_pre_flight script'.format(self.thin_dir))
elif not os.path.exists(self.ssh_pre_flight):
log.error('The ssh_pre_flight script {0} does not exist'.format(self.ssh_pre_flight))
else:
@ -1035,7 +1044,7 @@ class Single(object):
if stderr:
log.error('Error running ssh_pre_flight script {0}'.format(self.ssh_pre_file))
return stdout, stderr, retcode
log.debug('Successfully ran the ssh_pre_flight script: {0}'.format(self.ssh_pre_file))
log.info('Successfully ran the ssh_pre_flight script: {0}'.format(self.ssh_pre_file))
if self.opts.get("raw_shell", False):
cmd_str = " ".join([self._escape_arg(arg) for arg in self.argv])

View file

@ -786,6 +786,7 @@ VALID_OPTS = immutabletypes.freeze(
"ssh_log_file": six.string_types,
"ssh_config_file": six.string_types,
"ssh_merge_pillar": bool,
"ssh_run_pre_flight": bool,
"cluster_mode": bool,
"sqlite_queue_dir": six.string_types,
"queue_dirs": list,

View file

@ -3268,6 +3268,14 @@ class SaltSSHOptionParser(
help="Pass a JID to be used instead of generating one.",
)
self.add_option(
'--pre-flight',
default=False,
action='store_true',
dest='ssh_run_pre_flight',
help='Run the defined ssh_pre_flight script in the roster'
)
ssh_group = optparse.OptionGroup(
self, "SSH Options", "Parameters for the SSH client."
)

View file

@ -10,10 +10,6 @@ import shutil
# Import salt testing libs
from tests.support.case import SSHCase
from tests.support.runtests import RUNTIME_VARS
# Import salt libs
import salt.utils.yaml
class SSHTest(SSHCase):
@ -38,26 +34,6 @@ class SSHTest(SSHCase):
os.path.exists(os.path.join(thin_dir, "salt-call"))
os.path.exists(os.path.join(thin_dir, "running_data"))
def test_ssh_pre_flight(self):
'''
test ssh when ssh_pre_flight is set
ensure the script runs successfully
'''
roster = os.path.join(RUNTIME_VARS.TMP, 'pre_flight_roster')
data = {'ssh_pre_flight': os.path.join(RUNTIME_VARS.TMP, 'ssh_pre_flight.sh')}
self.custom_roster(roster, data)
test_script = os.path.join(RUNTIME_VARS.TMP,
'test-pre-flight-script-worked.txt')
with salt.utils.files.fopen(data['ssh_pre_flight'], 'w') as fp_:
fp_.write('touch {0}'.format(test_script))
ret = self.run_function('test.ping', roster_file=roster)
assert os.path.exists(test_script)
def tearDown(self):
"""
make sure to clean up any old ssh directories

View file

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
'''
Test for ssh_pre_flight roster option
'''
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
import os
import shutil
# Import salt testing libs
from tests.support.case import SSHCase
from tests.support.runtests import RUNTIME_VARS
# import salt libs
import salt.utils.files
class SSHPreFlightTest(SSHCase):
'''
Test ssh_pre_flight roster option
'''
def setUp(self):
self.roster = os.path.join(RUNTIME_VARS.TMP, 'pre_flight_roster')
self.data = {'ssh_pre_flight': os.path.join(RUNTIME_VARS.TMP, 'ssh_pre_flight.sh')}
self.test_script = os.path.join(RUNTIME_VARS.TMP,
'test-pre-flight-script-worked.txt')
def _create_roster(self):
self.custom_roster(self.roster, self.data)
with salt.utils.files.fopen(self.data['ssh_pre_flight'], 'w') as fp_:
fp_.write('touch {0}'.format(self.test_script))
def test_ssh_pre_flight(self):
'''
test ssh when ssh_pre_flight is set
ensure the script runs successfully
'''
self._create_roster()
ret = self.run_function('test.ping', roster_file=self.roster)
assert os.path.exists(self.test_script)
def test_ssh_run_pre_flight(self):
'''
test ssh when --pre-flight is passed to salt-ssh
to ensure the script runs successfully
'''
self._create_roster()
# make sure we previously ran a command so the thin dir exists
self.run_function('test.ping', wipe=False)
assert not os.path.exists(self.test_script)
ret = self.run_function('test.ping', ssh_opts='--pre-flight',
roster_file=self.roster, wipe=False)
assert os.path.exists(self.test_script)
def tearDown(self):
'''
make sure to clean up any old ssh directories
'''
files = [self.roster, self.data['ssh_pre_flight'], self.test_script]
for fp_ in files:
if os.path.exists(fp_):
os.remove(fp_)

View file

@ -85,19 +85,21 @@ class ShellTestCase(TestCase, AdaptedConfigurationTestCaseMixin, ScriptPathMixin
)
def run_ssh(self, arg_str, with_retcode=False, timeout=25,
catch_stderr=False, wipe=False, raw=False, roster_file=None, **kwargs):
catch_stderr=False, wipe=False, raw=False, roster_file=None,
ssh_opts='', **kwargs):
'''
Execute salt-ssh
'''
if not roster_file:
roster_file = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'roster')
arg_str = '{0} {1} -c {2} -i --priv {3} --roster-file {4} localhost {5} --out=json'.format(
arg_str = '{0} {1} -c {2} -i --priv {3} --roster-file {4} {5} localhost {6} --out=json'.format(
' -W' if wipe else '',
' -r' if raw else '',
self.config_dir,
os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'key_test'),
roster_file,
ssh_opts,
arg_str
)
return self.run_script(
@ -524,19 +526,21 @@ class ShellCase(ShellTestCase, AdaptedConfigurationTestCaseMixin, ScriptPathMixi
return ret
def run_ssh(self, arg_str, with_retcode=False, catch_stderr=False, # pylint: disable=W0221
timeout=RUN_TIMEOUT, wipe=True, raw=False, roster_file=None, **kwargs):
timeout=RUN_TIMEOUT, wipe=True, raw=False, roster_file=None,
ssh_opts='', **kwargs):
'''
Execute salt-ssh
'''
if not roster_file:
roster_file = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'roster')
arg_str = '{0} -ldebug{1} -c {2} -i --priv {3} --roster-file {4} --out=json localhost {5}'.format(
arg_str = '{0} -ldebug{1} -c {2} -i --priv {3} --roster-file {4} {5} --out=json localhost {6}'.format(
' -W' if wipe else '',
' -r' if raw else '',
self.config_dir,
os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'key_test'),
roster_file,
ssh_opts,
arg_str)
ret = self.run_script('salt-ssh',
arg_str,

View file

@ -177,7 +177,7 @@ class SSHSingleTests(TestCase):
mock_cmd = MagicMock(return_value=cmd_ret)
patch_flight = patch('salt.client.ssh.Single.run_ssh_pre_flight', mock_flight)
patch_cmd = patch('salt.client.ssh.Single.cmd_block', mock_cmd)
patch_os = patch('os.path.exists', side_effect=[False, True])
patch_os = patch('os.path.exists', side_effect=[True])
with patch_os, patch_flight, patch_cmd:
ret = single.run()
@ -207,7 +207,7 @@ class SSHSingleTests(TestCase):
mock_cmd = MagicMock(return_value=cmd_ret)
patch_flight = patch('salt.client.ssh.Single.run_ssh_pre_flight', mock_flight)
patch_cmd = patch('salt.client.ssh.Single.cmd_block', mock_cmd)
patch_os = patch('os.path.exists', side_effect=[False, True])
patch_os = patch('os.path.exists', side_effect=[True])
with patch_os, patch_flight, patch_cmd:
ret = single.run()
@ -237,7 +237,7 @@ class SSHSingleTests(TestCase):
mock_cmd = MagicMock(return_value=cmd_ret)
patch_flight = patch('salt.client.ssh.Single.run_ssh_pre_flight', mock_flight)
patch_cmd = patch('salt.client.ssh.Single.cmd_block', mock_cmd)
patch_os = patch('os.path.exists', side_effect=[False, False])
patch_os = patch('os.path.exists', side_effect=[False])
with patch_os, patch_flight, patch_cmd:
ret = single.run()
@ -262,11 +262,11 @@ class SSHSingleTests(TestCase):
mine=False,
**target)
cmd_ret = ('', 'Error running script', 1)
cmd_ret = ('', '', 0)
mock_flight = MagicMock(return_value=cmd_ret)
mock_cmd = MagicMock(return_value=cmd_ret)
patch_flight = patch('salt.client.ssh.Single.run_ssh_pre_flight', mock_flight)
patch_cmd = patch('salt.client.ssh.Single.cmd_block', mock_cmd)
patch_cmd = patch('salt.client.ssh.shell.Shell.exec_cmd', mock_cmd)
patch_os = patch('os.path.exists', return_value=True)
with patch_os, patch_flight, patch_cmd: