Compare commits

...

14 Commits

Author SHA1 Message Date
bc03a294ae Update release versions for 1.2.1 2013-07-04 21:47:47 -04:00
2e4e064a88 Update packaging changelogs. 2013-07-04 21:47:03 -04:00
032c8408af Update the changelog 2013-07-04 21:44:25 -04:00
c593454fd0 Improve interlaced output prevention when asking for host key approval. 2013-07-04 21:34:11 -04:00
d1eaf2db70 Default to 'smart' transport, which will use OpenSSH if it can support ControlPersist. 2013-07-04 21:33:58 -04:00
8cc2f1c4a9 Lock around SSH connectivity to new hosts in host checking mode such that prompts for host approval
messages do not get interlaced.
2013-07-04 21:31:14 -04:00
9f9373ccb5 Close the named temporary file. 2013-07-04 21:31:05 -04:00
3dd93ee2e4 Closing stdin too early. 2013-07-04 21:30:49 -04:00
f782c237c4 Enable paramiko to ask whether to add keys to known hosts. 2013-07-04 21:30:38 -04:00
2b58264096 Example config file should use correct config setting. 2013-07-04 21:30:27 -04:00
266e689403 Merge 2013-07-04 21:30:20 -04:00
13f93de78a Makes host key checking the default behavior but can be disabled in ansible.cfg or by environment variable. 2013-07-04 21:30:00 -04:00
8584a945d6 Merge pull request #3307 from mplonka/release1.2
Skipping facts not available in AIX WPARs
2013-06-27 19:24:11 -07:00
2b07873eb1 Skipping facts not available in AIX WPARs 2013-06-21 13:32:57 +02:00
17 changed files with 344 additions and 133 deletions

View File

@ -1,6 +1,15 @@
Ansible Changes By Release
==========================
1.2.1 "Hear About It Later" -- July 4, 2013
* Connection default is now "smart", which discovers if the system openssh can support ControlPersist, and uses
it if so, if not falls back to paramiko.
* Host key checking is on by default. Disable it if you like by adding host_key_checking=False in the [default]
section of /etc/ansible/ansible.cfg or ~/ansible.cfg or by exporting ANSIBLE_HOST_KEY_CHECKING=False
* Paramiko now records host keys it was in contact with host key checking is on. It is somewhat sluggish when doing this,
so switch to the 'ssh' transport if this concerns you.
1.2 "Right Now" -- June 10, 2013
Core Features:

View File

@ -1 +1,2 @@
1.2
1.2.1

View File

@ -2,12 +2,12 @@
.\" Title: ansible-doc
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 04/02/2013
.\" Date: 07/04/2013
.\" Manual: System administration commands
.\" Source: Ansible 1.2
.\" Source: Ansible 1.2.1
.\" Language: English
.\"
.TH "ANSIBLE\-DOC" "1" "04/02/2013" "Ansible 1\&.2" "System administration commands"
.TH "ANSIBLE\-DOC" "1" "07/04/2013" "Ansible 1\&.2\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------

View File

@ -2,12 +2,12 @@
.\" Title: ansible-playbook
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 06/03/2013
.\" Date: 07/04/2013
.\" Manual: System administration commands
.\" Source: Ansible 1.2
.\" Source: Ansible 1.2.1
.\" Language: English
.\"
.TH "ANSIBLE\-PLAYBOOK" "1" "06/03/2013" "Ansible 1\&.2" "System administration commands"
.TH "ANSIBLE\-PLAYBOOK" "1" "07/04/2013" "Ansible 1\&.2\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------

View File

@ -2,12 +2,12 @@
.\" Title: ansible
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 04/02/2013
.\" Date: 07/04/2013
.\" Manual: System administration commands
.\" Source: Ansible 1.2
.\" Source: Ansible 1.2.1
.\" Language: English
.\"
.TH "ANSIBLE" "1" "04/02/2013" "Ansible 1\&.2" "System administration commands"
.TH "ANSIBLE" "1" "07/04/2013" "Ansible 1\&.2\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@ -22,7 +22,7 @@
ansible-pull \- set up a remote copy of ansible on each managed node
.SH "SYNOPSIS"
.sp
ansible \-d DEST \-U URL [ \-C CHECKOUT ]
ansible \-d DEST \-U URL [ \-C CHECKOUT ] [ \-i INVENTORY ] [ <filename\&.yml> ]
.SH "DESCRIPTION"
.sp
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&.
@ -32,6 +32,13 @@ Use ansible\-pull to set up a remote copy of ansible on each managed node, each
The setup playbook can be tuned to change the cron frequency, logging locations, and parameters to ansible\-pull\&.
.sp
This is useful both for extreme scale\-out as well as periodic remediation\&. Usage of the \fIfetch\fR module to retrieve logs from ansible\-pull runs would be an excellent way to gather and analyze remote logs from ansible\-pull\&.
.SH "OPTIONAL ARGUMENT"
.PP
\fBfilename\&.yml\fR
.RS 4
The name of one the YAML format files to run as an ansible playbook\&. This can be a relative path within the git checkout\&. If not provided, ansible\-pull will look for a playbook based on the host\(cqs fully\-qualified domain name and finally a playbook named
\fBlocal\&.yml\fR\&.
.RE
.SH "OPTIONS"
.PP
\fB\-d\fR \fIDEST\fR, \fB\-\-directory=\fR\fIDEST\fR
@ -49,6 +56,18 @@ URL of git repository to clone\&.
Branch/Tag/Commit to checkout\&. Defaults to
\fIHEAD\fR\&.
.RE
.PP
\fB\-i\fR \fIPATH\fR, \fB\-\-inventory=\fR\fIPATH\fR
.RS 4
The
\fIPATH\fR
to the inventory hosts file\&. This can be a relative path within the git checkout\&.
.RE
.PP
\fB\-\-purge\fR
.RS 4
Purge the git checkout after the playbook is run\&.
.RE
.SH "AUTHOR"
.sp
Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&.

View File

@ -2,12 +2,12 @@
.\" Title: ansible
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 05/29/2013
.\" Date: 07/04/2013
.\" Manual: System administration commands
.\" Source: Ansible 1.2
.\" Source: Ansible 1.2.1
.\" Language: English
.\"
.TH "ANSIBLE" "1" "05/29/2013" "Ansible 1\&.2" "System administration commands"
.TH "ANSIBLE" "1" "07/04/2013" "Ansible 1\&.2\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------

View File

@ -9,74 +9,24 @@
hostfile = /etc/ansible/hosts
# location of ansible library, eliminates need to specify --module-path
library = /usr/share/ansible
hostfile = /etc/ansible/hosts
library = /usr/share/ansible
remote_tmp = $HOME/.ansible/tmp
pattern = *
forks = 5
poll_interval = 15
sudo_user = root
#ask_sudo_pass = True
#ask_pass = True
transport = smart
remote_port = 22
library = /usr/share/ansible
# default module name used in /usr/bin/ansible when -m is not specified
# uncomment this to disable SSH key host checking
#host_key_checking = False
module_name = command
# location for ansible log file. If set, will store output from ansible
# and ansible-playbook. If enabling, you may wish to configure
# logrotate.
#log_path = /var/log/ansible.log
# home directory where temp files are stored on remote systems. Should
# almost always contain $HOME or be a directory writeable by all users
remote_tmp = $HOME/.ansible/tmp
# the default pattern for ansible-playbooks ("hosts:")
pattern = *
# the default number of forks (parallelism) to be used. Usually you
# can crank this up.
forks=5
# the timeout used by various connection types. Usually this corresponds
# to an SSH timeout
timeout=10
# when using --poll or "poll:" in an ansible playbook, and not specifying
# an explicit poll interval, use this interval
poll_interval=15
# when specifying --sudo to /usr/bin/ansible or "sudo:" in a playbook,
# and not specifying "--sudo-user" or "sudo_user" respectively, sudo
# to this user account
sudo_user=root
# the following forces ansible to always ask for the sudo password (instead of having
# to add -K to the commandline). Or you can use the environment variable (ANSIBLE_ASK_SUDO_PASS)
#ask_sudo_pass=True
# the following forces ansible to always ask for the ssh-password (-k)
# can also be set by the environment variable ANSIBLE_ASK_PASS
#ask_pass=True
# connection to use when -c <connection_type> is not specified
transport=paramiko
# remote SSH port to be used when --port or "port:" or an equivalent inventory
# variable is not specified.
remote_port=22
# if set, always run /usr/bin/ansible commands as this user, and assume this value
# if "user:" is not set in a playbook. If not set, use the current Unix user
# as the default
#remote_user=root
# the default sudo executable. If a sudo alternative with a sudo-compatible interface
# is used, specify its executable name as the default
@ -152,7 +102,7 @@ filter_plugins = /usr/share/ansible_plugins/filter_plugins
# will result in poor performance, so use transport=paramiko on older platforms rather than
# removing it
ssh_args=-o ControlMaster=auto -o ControlPersist=60s -o ControlPath=/tmp/ansible-ssh-%h-%p-%r
ssh_args=#-o ControlMaster=auto -o ControlPersist=60s -o ControlPath=/tmp/ansible-ssh-%h-%p-%r
# the following makes ansible use scp if the connection type is ssh (default is sftp)

View File

@ -14,5 +14,5 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
__version__ = '1.2'
__version__ = '1.2.1'
__author__ = 'Michael DeHaan'

View File

@ -79,11 +79,23 @@ def log_lockfile():
LOG_LOCK = open(log_lockfile(), 'w')
def log_flock():
fcntl.flock(LOG_LOCK, fcntl.LOCK_EX)
def log_flock(runner):
fcntl.lockf(LOG_LOCK, fcntl.LOCK_EX)
if runner is not None:
try:
fcntl.lockf(runner.output_lockfile, fcntl.LOCK_EX)
except OSError, e:
# already got closed?
pass
def log_unflock():
fcntl.flock(LOG_LOCK, fcntl.LOCK_UN)
def log_unflock(runner):
fcntl.lockf(LOG_LOCK, fcntl.LOCK_UN)
if runner is not None:
try:
fcntl.lockf(runner.output_lockfile, fcntl.LOCK_UN)
except OSError, e:
# already got closed?
pass
def set_play(callback, play):
''' used to notify callback plugins of context '''
@ -97,9 +109,9 @@ def set_task(callback, task):
for callback_plugin in callback_plugins:
callback_plugin.task = task
def display(msg, color=None, stderr=False, screen_only=False, log_only=False):
def display(msg, color=None, stderr=False, screen_only=False, log_only=False, runner=None):
# prevent a very rare case of interlaced multiprocess I/O
log_flock()
log_flock(runner)
msg2 = msg
if color:
msg2 = stringc(msg, color)
@ -116,7 +128,7 @@ def display(msg, color=None, stderr=False, screen_only=False, log_only=False):
logger.error(msg)
else:
logger.info(msg)
log_unflock()
log_unflock(runner)
def call_callback_module(method_name, *args, **kwargs):
@ -342,7 +354,7 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks):
def on_unreachable(self, host, res):
if type(res) == dict:
res = res.get('msg','')
display("%s | FAILED => %s" % (host, res), stderr=True, color='red')
display("%s | FAILED => %s" % (host, res), stderr=True, color='red', runner=self.runner)
if self.options.tree:
utils.write_tree_file(
self.options.tree, host,
@ -351,15 +363,15 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks):
super(CliRunnerCallbacks, self).on_unreachable(host, res)
def on_skipped(self, host, item=None):
display("%s | skipped" % (host))
display("%s | skipped" % (host), runner=self.runner)
super(CliRunnerCallbacks, self).on_skipped(host, item)
def on_error(self, host, err):
display("err: [%s] => %s\n" % (host, err), stderr=True)
display("err: [%s] => %s\n" % (host, err), stderr=True, runner=self.runner)
super(CliRunnerCallbacks, self).on_error(host, err)
def on_no_hosts(self):
display("no hosts matched\n", stderr=True)
display("no hosts matched\n", stderr=True, runner=self.runner)
super(CliRunnerCallbacks, self).on_no_hosts()
def on_async_poll(self, host, res, jid, clock):
@ -367,27 +379,27 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks):
self._async_notified[jid] = clock + 1
if self._async_notified[jid] > clock:
self._async_notified[jid] = clock
display("<job %s> polling, %ss remaining" % (jid, clock))
display("<job %s> polling, %ss remaining" % (jid, clock), runner=self.runner)
super(CliRunnerCallbacks, self).on_async_poll(host, res, jid, clock)
def on_async_ok(self, host, res, jid):
display("<job %s> finished on %s => %s"%(jid, host, utils.jsonify(res,format=True)))
display("<job %s> finished on %s => %s"%(jid, host, utils.jsonify(res,format=True)), runner=self.runner)
super(CliRunnerCallbacks, self).on_async_ok(host, res, jid)
def on_async_failed(self, host, res, jid):
display("<job %s> FAILED on %s => %s"%(jid, host, utils.jsonify(res,format=True)), color='red', stderr=True)
display("<job %s> FAILED on %s => %s"%(jid, host, utils.jsonify(res,format=True)), color='red', stderr=True, runner=self.runner)
super(CliRunnerCallbacks, self).on_async_failed(host,res,jid)
def _on_any(self, host, result):
result2 = result.copy()
result2.pop('invocation', None)
(msg, color) = host_report_msg(host, self.options.module_name, result2, self.options.one_line)
display(msg, color=color)
display(msg, color=color, runner=self.runner)
if self.options.tree:
utils.write_tree_file(self.options.tree, host, utils.jsonify(result2,format=True))
def on_file_diff(self, host, diff):
display(utils.get_diff(diff))
display(utils.get_diff(diff), runner=self.runner)
super(CliRunnerCallbacks, self).on_file_diff(host, diff)
########################################################################
@ -408,11 +420,12 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
msg = "fatal: [%s] => (item=%s) => %s" % (host, item, results)
else:
msg = "fatal: [%s] => %s" % (host, results)
display(msg, color='red')
display(msg, color='red', runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_unreachable(host, results)
def on_failed(self, host, results, ignore_errors=False):
results2 = results.copy()
results2.pop('invocation', None)
@ -429,21 +442,22 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
msg = "failed: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(results2))
else:
msg = "failed: [%s] => %s" % (host, utils.jsonify(results2))
display(msg, color='red')
display(msg, color='red', runner=self.runner)
if stderr:
display("stderr: %s" % stderr, color='red')
display("stderr: %s" % stderr, color='red', runner=self.runner)
if stdout:
display("stdout: %s" % stdout, color='red')
display("stdout: %s" % stdout, color='red', runner=self.runner)
if returned_msg:
display("msg: %s" % returned_msg, color='red')
display("msg: %s" % returned_msg, color='red', runner=self.runner)
if not parsed and module_msg:
display("invalid output was: %s" % module_msg, color='red')
display("invalid output was: %s" % module_msg, color='red', runner=self.runner)
if ignore_errors:
display("...ignoring", color='cyan')
display("...ignoring", color='cyan', runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_failed(host, results, ignore_errors=ignore_errors)
def on_ok(self, host, host_result):
item = host_result.get('item', None)
host_result2 = host_result.copy()
@ -473,9 +487,9 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
if msg != '':
if not changed:
display(msg, color='green')
display(msg, color='green', runner=self.runner)
else:
display(msg, color='yellow')
display(msg, color='yellow', runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_ok(host, host_result)
def on_error(self, host, err):
@ -487,7 +501,7 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
else:
msg = "err: [%s] => %s" % (host, err)
display(msg, color='red', stderr=True)
display(msg, color='red', stderr=True, runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_error(host, err)
def on_skipped(self, host, item=None):
@ -496,11 +510,11 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
msg = "skipping: [%s] => (item=%s)" % (host, item)
else:
msg = "skipping: [%s]" % host
display(msg, color='cyan')
display(msg, color='cyan', runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_skipped(host, item)
def on_no_hosts(self):
display("FATAL: no hosts matched or all hosts have already failed -- aborting\n", color='red')
display("FATAL: no hosts matched or all hosts have already failed -- aborting\n", color='red', runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_no_hosts()
def on_async_poll(self, host, res, jid, clock):
@ -509,21 +523,21 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
if self._async_notified[jid] > clock:
self._async_notified[jid] = clock
msg = "<job %s> polling, %ss remaining"%(jid, clock)
display(msg, color='cyan')
display(msg, color='cyan', runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_async_poll(host,res,jid,clock)
def on_async_ok(self, host, res, jid):
msg = "<job %s> finished on %s"%(jid, host)
display(msg, color='cyan')
display(msg, color='cyan', runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_async_ok(host, res, jid)
def on_async_failed(self, host, res, jid):
msg = "<job %s> FAILED on %s" % (jid, host)
display(msg, color='red', stderr=True)
display(msg, color='red', stderr=True, runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_async_failed(host,res,jid)
def on_file_diff(self, host, diff):
display(utils.get_diff(diff))
display(utils.get_diff(diff), runner=self.runner)
super(PlaybookRunnerCallbacks, self).on_file_diff(host, diff)
########################################################################

View File

@ -20,7 +20,25 @@ import pwd
import sys
import ConfigParser
def get_config(p, section, key, env_var, default):
# copied from utils, avoid circular reference fun :)
def mk_boolean(value):
val = str(value)
if val.lower() in [ "true", "t", "y", "1", "yes" ]:
return True
else:
return False
def get_config(p, section, key, env_var, default, boolean=False, integer=False):
''' return a configuration variable with casting '''
value = _get_config(p, section, key, env_var, default)
if boolean:
return mk_boolean(value)
if integer:
return int(value)
return value
def _get_config(p, section, key, env_var, default, boolean=True):
''' helper function for get_config '''
if env_var is not None:
value = os.environ.get(env_var, None)
if value is not None:
@ -81,20 +99,20 @@ DEFAULT_MODULE_LANG = get_config(p, DEFAULTS, 'module_lang', 'ANSIBLE
DEFAULT_TIMEOUT = get_config(p, DEFAULTS, 'timeout', 'ANSIBLE_TIMEOUT', 10)
DEFAULT_POLL_INTERVAL = get_config(p, DEFAULTS, 'poll_interval', 'ANSIBLE_POLL_INTERVAL', 15)
DEFAULT_REMOTE_USER = get_config(p, DEFAULTS, 'remote_user', 'ANSIBLE_REMOTE_USER', active_user)
DEFAULT_ASK_PASS = get_config(p, DEFAULTS, 'ask_pass', 'ANSIBLE_ASK_PASS', False)
DEFAULT_ASK_PASS = get_config(p, DEFAULTS, 'ask_pass', 'ANSIBLE_ASK_PASS', False, boolean=True)
DEFAULT_PRIVATE_KEY_FILE = shell_expand_path(get_config(p, DEFAULTS, 'private_key_file', 'ANSIBLE_PRIVATE_KEY_FILE', None))
DEFAULT_SUDO_USER = get_config(p, DEFAULTS, 'sudo_user', 'ANSIBLE_SUDO_USER', 'root')
DEFAULT_ASK_SUDO_PASS = get_config(p, DEFAULTS, 'ask_sudo_pass', 'ANSIBLE_ASK_SUDO_PASS', False)
DEFAULT_ASK_SUDO_PASS = get_config(p, DEFAULTS, 'ask_sudo_pass', 'ANSIBLE_ASK_SUDO_PASS', False, boolean=True)
DEFAULT_REMOTE_PORT = int(get_config(p, DEFAULTS, 'remote_port', 'ANSIBLE_REMOTE_PORT', 22))
DEFAULT_TRANSPORT = get_config(p, DEFAULTS, 'transport', 'ANSIBLE_TRANSPORT', 'paramiko')
DEFAULT_SCP_IF_SSH = get_config(p, 'ssh_connection', 'scp_if_ssh', 'ANSIBLE_SCP_IF_SSH', False)
DEFAULT_TRANSPORT = get_config(p, DEFAULTS, 'transport', 'ANSIBLE_TRANSPORT', 'smart')
DEFAULT_SCP_IF_SSH = get_config(p, 'ssh_connection', 'scp_if_ssh', 'ANSIBLE_SCP_IF_SSH', False, boolean=True)
DEFAULT_MANAGED_STR = get_config(p, DEFAULTS, 'ansible_managed', None, 'Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}')
DEFAULT_SYSLOG_FACILITY = get_config(p, DEFAULTS, 'syslog_facility', 'ANSIBLE_SYSLOG_FACILITY', 'LOG_USER')
DEFAULT_KEEP_REMOTE_FILES = get_config(p, DEFAULTS, 'keep_remote_files', 'ANSIBLE_KEEP_REMOTE_FILES', '0')
DEFAULT_SUDO_EXE = get_config(p, DEFAULTS, 'sudo_exe', 'ANSIBLE_SUDO_EXE', 'sudo')
DEFAULT_SUDO_FLAGS = get_config(p, DEFAULTS, 'sudo_flags', 'ANSIBLE_SUDO_FLAGS', '-H')
DEFAULT_HASH_BEHAVIOUR = get_config(p, DEFAULTS, 'hash_behaviour', 'ANSIBLE_HASH_BEHAVIOUR', 'replace')
DEFAULT_LEGACY_PLAYBOOK_VARIABLES = get_config(p, DEFAULTS, 'legacy_playbook_variables', 'ANSIBLE_LEGACY_PLAYBOOK_VARIABLES', 'yes')
DEFAULT_LEGACY_PLAYBOOK_VARIABLES = get_config(p, DEFAULTS, 'legacy_playbook_variables', 'ANSIBLE_LEGACY_PLAYBOOK_VARIABLES', True, boolean=True)
DEFAULT_JINJA2_EXTENSIONS = get_config(p, DEFAULTS, 'jinja2_extensions', 'ANSIBLE_JINJA2_EXTENSIONS', None)
DEFAULT_EXECUTABLE = get_config(p, DEFAULTS, 'executable', 'ANSIBLE_EXECUTABLE', '/bin/sh')
@ -110,6 +128,8 @@ ANSIBLE_NOCOWS = get_config(p, DEFAULTS, 'nocows', 'ANSIBLE_NOCO
ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None)
ZEROMQ_PORT = int(get_config(p, 'fireball', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099))
HOST_KEY_CHECKING = get_config(p, DEFAULTS, 'host_key_checking', 'ANSIBLE_HOST_KEY_CHECKING', True, boolean=True)
# non-configurable things
DEFAULT_SUDO_PASS = None
DEFAULT_REMOTE_PASS = None

View File

@ -30,6 +30,7 @@ import base64
import sys
import shlex
import pipes
import subprocess
import ansible.constants as C
import ansible.inventory
@ -49,10 +50,13 @@ except ImportError:
HAS_ATFORK=False
multiprocessing_runner = None
OUTPUT_LOCKFILE = tempfile.TemporaryFile()
PROCESS_LOCKFILE = tempfile.TemporaryFile()
################################################
def _executor_hook(job_queue, result_queue):
def _executor_hook(job_queue, result_queue, new_stdin):
# attempt workaround of https://github.com/newsapps/beeswithmachineguns/issues/17
# this function also not present in CentOS 6
@ -63,7 +67,7 @@ def _executor_hook(job_queue, result_queue):
while not job_queue.empty():
try:
host = job_queue.get(block=False)
return_data = multiprocessing_runner._executor(host)
return_data = multiprocessing_runner._executor(host, new_stdin)
result_queue.put(return_data)
if 'LEGACY_TEMPLATE_WARNING' in return_data.flags:
@ -131,6 +135,10 @@ class Runner(object):
complex_args=None # structured data in addition to module_args, must be a dict
):
# used to lock multiprocess inputs and outputs at various levels
self.output_lockfile = OUTPUT_LOCKFILE
self.process_lockfile = PROCESS_LOCKFILE
if not complex_args:
complex_args = {}
@ -163,9 +171,19 @@ class Runner(object):
self.is_playbook = is_playbook
self.environment = environment
self.complex_args = complex_args
self.callbacks.runner = self
# if the transport is 'smart' see if SSH can support ControlPersist if not use paramiko
# 'smart' is the default since 1.2.1/1.3
if self.transport == 'smart':
cmd = subprocess.Popen(['ssh','-o','ControlPersist'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
if "Bad configuration option" in err:
self.transport = "paramiko"
else:
self.transport = "ssh"
# misc housekeeping
if subset and self.inventory._subset is None:
# don't override subset when passed from playbook
@ -328,7 +346,7 @@ class Runner(object):
# *****************************************************
def _executor(self, host):
def _executor(self, host, new_stdin):
''' handler for multiprocessing library '''
def get_flags():
@ -341,7 +359,9 @@ class Runner(object):
return flags
try:
exec_rc = self._executor_internal(host)
self._new_stdin = new_stdin
exec_rc = self._executor_internal(host, new_stdin)
if type(exec_rc) != ReturnData:
raise Exception("unexpected return type: %s" % type(exec_rc))
exec_rc.flags = get_flags()
@ -360,7 +380,7 @@ class Runner(object):
# *****************************************************
def _executor_internal(self, host):
def _executor_internal(self, host, new_stdin):
''' executes any module one or more times '''
host_variables = self.inventory.get_variables(host)
@ -759,8 +779,9 @@ class Runner(object):
workers = []
for i in range(self.forks):
new_stdin = os.fdopen(os.dup(sys.stdin.fileno()))
prc = multiprocessing.Process(target=_executor_hook,
args=(job_queue, result_queue))
args=(job_queue, result_queue, new_stdin))
prc.start()
workers.append(prc)
@ -771,7 +792,7 @@ class Runner(object):
for worker in workers:
worker.terminate()
worker.join()
results = []
try:
while not result_queue.empty():
@ -832,7 +853,7 @@ class Runner(object):
# We aren't iterating over all the hosts in this
# group. So, just pick the first host in our group to
# construct the conn object with.
result_data = self._executor(hosts[0]).result
result_data = self._executor(hosts[0], None).result
# Create a ResultData item for each host in this group
# using the returned result. If we didn't do this we would
# get false reports of dark hosts.
@ -850,7 +871,8 @@ class Runner(object):
raise errors.AnsibleError("interrupted")
raise
else:
results = [ self._executor(h) for h in hosts ]
results = [ self._executor(h, None) for h in hosts ]
return self._partition_results(results)
# *****************************************************

View File

@ -15,15 +15,35 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# ---
# The paramiko transport is provided because many distributions, in particular EL6 and before
# do not support ControlPersist in their SSH implementations. This is needed on the Ansible
# control machine to be reasonably efficient with connections. Thus paramiko is faster
# for most users on these platforms. Users with ControlPersist capability can consider
# using -c ssh or configuring the transport in ansible.cfg.
import warnings
import os
import pipes
import socket
import random
import logging
import traceback
import fcntl
import sys
from termios import tcflush, TCIFLUSH
from binascii import hexlify
from ansible.callbacks import vvv
from ansible import errors
from ansible import utils
from ansible import constants as C
AUTHENTICITY_MSG="""
paramiko: The authenticity of host '%s' can't be established.
The %s key fingerprint is %s.
Are you sure you want to continue connecting (yes/no)?
"""
# prevent paramiko warning noise -- see http://stackoverflow.com/questions/3920502/
HAVE_PARAMIKO=False
@ -36,12 +56,58 @@ with warnings.catch_warnings():
except ImportError:
pass
class MyAddPolicy(object):
"""
Based on AutoAddPolicy in paramiko so we can determine when keys are added
and also prompt for input.
Policy for automatically adding the hostname and new host key to the
local L{HostKeys} object, and saving it. This is used by L{SSHClient}.
"""
def __init__(self, runner):
self.runner = runner
def missing_host_key(self, client, hostname, key):
if C.HOST_KEY_CHECKING:
fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX)
fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX)
old_stdin = sys.stdin
sys.stdin = self.runner._new_stdin
fingerprint = hexlify(key.get_fingerprint())
ktype = key.get_name()
# clear out any premature input on sys.stdin
tcflush(sys.stdin, TCIFLUSH)
inp = raw_input(AUTHENTICITY_MSG % (hostname, ktype, fingerprint))
sys.stdin = old_stdin
if inp not in ['yes','y','']:
fcntl.flock(self.runner.output_lockfile, fcntl.LOCK_UN)
fcntl.flock(self.runner.process_lockfile, fcntl.LOCK_UN)
raise errors.AnsibleError("host connection rejected by user")
fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_UN)
fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN)
key._added_by_ansible_this_time = True
# existing implementation below:
client._host_keys.add(hostname, key.get_name(), key)
# host keys are actually saved in close() function below
# in order to control ordering.
# keep connection objects on a per host basis to avoid repeated attempts to reconnect
SSH_CONNECTION_CACHE = {}
SFTP_CONNECTION_CACHE = {}
class Connection(object):
''' SSH based connections with Paramiko '''
@ -76,7 +142,12 @@ class Connection(object):
vvv("ESTABLISH CONNECTION FOR USER: %s on PORT %s TO %s" % (self.user, self.port, self.host), host=self.host)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.keyfile = os.path.expanduser("~/.ssh/known_hosts")
if C.HOST_KEY_CHECKING:
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(MyAddPolicy(self.runner))
allow_agent = True
if self.password is not None:
@ -188,6 +259,42 @@ class Connection(object):
except IOError:
raise errors.AnsibleError("failed to transfer file from %s" % in_path)
def _any_keys_added(self):
added_any = False
for hostname, keys in self.ssh._host_keys.iteritems():
for keytype, key in keys.iteritems():
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
if added_this_time:
return True
return False
def _save_ssh_host_keys(self, filename):
'''
not using the paramiko save_ssh_host_keys function as we want to add new SSH keys at the bottom so folks
don't complain about it :)
'''
if not self._any_keys_added():
return False
path = os.path.expanduser("~/.ssh")
if not os.path.exists(path):
os.makedirs(path)
f = open(filename, 'w')
for hostname, keys in self.ssh._host_keys.iteritems():
for keytype, key in keys.iteritems():
# was f.write
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
if not added_this_time:
f.write("%s %s %s\n" % (hostname, keytype, key.get_base64()))
for hostname, keys in self.ssh._host_keys.iteritems():
for keytype, key in keys.iteritems():
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
if added_this_time:
f.write("%s %s %s\n" % (hostname, keytype, key.get_base64()))
f.close()
def close(self):
''' terminate the connection '''
cache_key = self._cache_key()
@ -195,5 +302,23 @@ class Connection(object):
SFTP_CONNECTION_CACHE.pop(cache_key, None)
if self.sftp is not None:
self.sftp.close()
if self._any_keys_added():
# add any new SSH host keys -- warning -- this could be slow
lockfile = self.keyfile.replace("known_hosts",".known_hosts.lock")
KEY_LOCK = open(lockfile, 'w')
fcntl.lockf(KEY_LOCK, fcntl.LOCK_EX)
try:
# just in case any were added recently
self.ssh.load_system_host_keys()
self.ssh._host_keys.update(self.ssh._system_host_keys)
self._save_ssh_host_keys(self.keyfile)
except:
# unable to save keys, including scenario when key was invalid
# and caught earlier
traceback.print_exc()
pass
fcntl.lockf(KEY_LOCK, fcntl.LOCK_UN)
self.ssh.close()

View File

@ -53,7 +53,10 @@ class Connection(object):
self.common_args += ["-o", "ControlMaster=auto",
"-o", "ControlPersist=60s",
"-o", "ControlPath=/tmp/ansible-ssh-%h-%p-%r"]
self.common_args += ["-o", "StrictHostKeyChecking=no"]
if not C.HOST_KEY_CHECKING:
self.common_args += ["-o", "StrictHostKeyChecking=no"]
if self.port is not None:
self.common_args += ["-o", "Port=%d" % (self.port)]
if self.private_key_file is not None:
@ -90,6 +93,22 @@ class Connection(object):
os.write(self.wfd, "%s\n" % self.password)
os.close(self.wfd)
def not_in_host_file(self, host):
host_file = os.path.expanduser("~/.ssh/known_hosts")
if not os.path.exists(host_file):
print "previous known host file not found"
return True
host_fh = open(host_file)
data = host_fh.read()
host_fh.close()
for line in data.split("\n"):
if line is None or line.find(" ") == -1:
continue
tokens = line.split()
if host in tokens[0]:
return False
return True
def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False, executable='/bin/sh'):
''' run a command on the remote host '''
@ -106,6 +125,17 @@ class Connection(object):
ssh_cmd.append(sudocmd)
vvv("EXEC %s" % ssh_cmd, host=self.host)
not_in_host_file = self.not_in_host_file(self.host)
if C.HOST_KEY_CHECKING and not_in_host_file:
# lock around the initial SSH connectivity so the user prompt about whether to add
# the host to known hosts is not intermingled with multiprocess output.
fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX)
fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX)
try:
# Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors
import pty
@ -158,6 +188,12 @@ class Connection(object):
elif p.poll() is not None:
break
stdin.close() # close stdin after we read from stdout (see also issue #848)
if C.HOST_KEY_CHECKING and not_in_host_file:
# lock around the initial SSH connectivity so the user prompt about whether to add
# the host to known hosts is not intermingled with multiprocess output.
fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_UN)
fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN)
if p.returncode != 0 and stderr.find('Bad configuration option: ControlPersist') != -1:
raise errors.AnsibleError('using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS="" (or ansible_ssh_args in the config file) before running again')

View File

@ -19,7 +19,7 @@ import os
import sys
import glob
import imp
import ansible.constants as C
from ansible import constants as C
from ansible import errors
MODULE_CACHE = {}

View File

@ -995,6 +995,9 @@ class AIX(Hardware):
rc, out, err = module.run_command("/usr/sbin/lsdev -Cc processor")
if not out:
# CPU info may not be available (WPAR)
return
i = 0
for line in out.split('\n'):
@ -1032,6 +1035,9 @@ class AIX(Hardware):
# /dev/ada0p3 314368 0 314368 0%
#
rc, out, err = module.run_command("/usr/sbin/lsps -s")
if not out:
# swapinfo may not be available (WPAR)
return
lines = out.split('\n')
data = lines[1].split()
swaptotal_mb = long(data[0].rstrip('MB'))

View File

@ -1,3 +1,9 @@
ansible (1.2.1) unstable; urgency=low
* 1.2.1 release
-- Michael DeHaan <michael.dehaan@gmail.com> Thu, 04 July 2013 21:45:00 -0500
ansible (1.2) unstable; urgency=low
* 1.2 release

View File

@ -6,7 +6,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Name: ansible
Release: 1%{?dist}
Summary: SSH-based configuration management, deployment, and orchestration engine
Version: 1.2
Version: 1.2.1
Group: Development/Libraries
License: GPLv3
@ -116,6 +116,9 @@ rm -rf $RPM_BUILD_ROOT
%changelog
* Thu Jul 04 2013 Michael DeHaan <michael.dehaan@gmail.com> - 1.2-1
* Release 1.2.1
* Mon Jun 10 2013 Michael DeHaan <michael.dehaan@gmail.com> - 1.2-0
* Release 1.2