mirror of
https://github.com/saltstack/salt.git
synced 2025-04-15 09:10:20 +00:00
add --pre-flight ssh option
This commit is contained in:
parent
4fa68c9dce
commit
0c365c19a4
12 changed files with 134 additions and 39 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -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``
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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."
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
65
tests/integration/ssh/test_pre_flight.py
Normal file
65
tests/integration/ssh/test_pre_flight.py
Normal 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_)
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue