Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d370bff94 | |||
| 531fd13992 | |||
| d5948d59fc | |||
| b33ca49289 | |||
| ac112b4dab | |||
| 863b88ba44 | |||
| 1b3d1125d1 | |||
| 690048c797 | |||
| d21224a7c9 | |||
| 1f2136b6b3 | |||
| 90c8dcc4b8 | |||
| f96ab83a1e | |||
| dfb2dbe26d | |||
| 853e7fb57c | |||
| 6241236d3c | |||
| 30ca72bfd2 | |||
| d4a595b207 | |||
| 715f25efbc | |||
| d5cf125757 | |||
| f29c98c29f | |||
| bc03a294ae | |||
| 2e4e064a88 | |||
| 032c8408af | |||
| c593454fd0 | |||
| d1eaf2db70 | |||
| 8cc2f1c4a9 | |||
| 9f9373ccb5 | |||
| 3dd93ee2e4 | |||
| f782c237c4 | |||
| 2b58264096 | |||
| 266e689403 | |||
| 13f93de78a | |||
| 8584a945d6 | |||
| 2b07873eb1 |
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,6 +1,23 @@
|
||||
Ansible Changes By Release
|
||||
==========================
|
||||
|
||||
1.2.1 "Hear About It Later" (reprise) -- July 4, 2013
|
||||
|
||||
* Added a configuration file option [paramiko_connection] record_host_keys which allows the code that paramiko uses
|
||||
to update known_hosts to be disabled. This is done because paramiko can be very slow at doing this if you have a
|
||||
large number of hosts and some folks may not want this behavior. This can be toggled independently of host key checking
|
||||
and does not affect the ssh transport plugin. Use of the ssh transport plugin is preferred if you have ControlPersist
|
||||
capability, and Ansible by default in 1.2.1 and later will autodetect.
|
||||
|
||||
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:
|
||||
|
||||
27
RELEASES.txt
27
RELEASES.txt
@ -1,17 +1,20 @@
|
||||
Ansible Releases at a Glance
|
||||
============================
|
||||
|
||||
1.2 "Right Now" ------- pending
|
||||
1.1 "Mean Street" ----- 04-02-2013
|
||||
1.0 "Eruption" -------- 02-01-2013
|
||||
0.9 "Dreams" ---------- 11-30-2012
|
||||
0.8 "Cathedral" ------- 10-19-2012
|
||||
0.7 "Panama" ---------- 09-06-2012
|
||||
0.6 "Cabo" ------------ 08-06-2012
|
||||
0.5 "Amsterdam" ------- 07-04-2012
|
||||
0.4 "Unchained" ------- 05-23-2012
|
||||
0.3 "Baluchitherium" -- 04-23-2012
|
||||
0.0.2 Untitled
|
||||
0.0.1 Untitled
|
||||
1.2.3 "Hear About It Later" -- 08-21-2013
|
||||
1.2.2 "Hear About It Later" -- 07-05-2013
|
||||
1.2.1 "Hear About It Later" -- 07-04-2013
|
||||
1.2 "Right Now" ------------ 06-10-2013
|
||||
1.1 "Mean Street" ---------- 04-02-2013
|
||||
1.0 "Eruption" ------------- 02-01-2013
|
||||
0.9 "Dreams" --------------- 11-30-2012
|
||||
0.8 "Cathedral" ------------ 10-19-2012
|
||||
0.7 "Panama" --------------- 09-06-2012
|
||||
0.6 "Cabo" ----------------- 08-06-2012
|
||||
0.5 "Amsterdam" ------------ 07-04-2012
|
||||
0.4 "Unchained" ------------ 05-23-2012
|
||||
0.3 "Baluchitherium" ------- 04-23-2012
|
||||
0.0.2 Untitled
|
||||
0.0.1 Untitled
|
||||
|
||||
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
'\" t
|
||||
.\" Title: ansible-doc
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
|
||||
.\" Date: 04/02/2013
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 08/21/2013
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.2
|
||||
.\" Source: Ansible 1.2.3
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-DOC" "1" "04/02/2013" "Ansible 1\&.2" "System administration commands"
|
||||
.TH "ANSIBLE\-DOC" "1" "08/21/2013" "Ansible 1\&.2\&.3" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
'\" t
|
||||
.\" Title: ansible-playbook
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
|
||||
.\" Date: 06/03/2013
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 08/21/2013
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.2
|
||||
.\" Source: Ansible 1.2.3
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-PLAYBOOK" "1" "06/03/2013" "Ansible 1\&.2" "System administration commands"
|
||||
.TH "ANSIBLE\-PLAYBOOK" "1" "08/21/2013" "Ansible 1\&.2\&.3" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
'\" t
|
||||
.\" Title: ansible
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
|
||||
.\" Date: 04/02/2013
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 08/21/2013
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.2
|
||||
.\" Source: Ansible 1.2.3
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "04/02/2013" "Ansible 1\&.2" "System administration commands"
|
||||
.TH "ANSIBLE" "1" "08/21/2013" "Ansible 1\&.2\&.3" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
@ -22,16 +31,23 @@
|
||||
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\&.
|
||||
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&.
|
||||
.sp
|
||||
Use ansible\-pull to set up a remote copy of ansible on each managed node, each set to run via cron and update playbook source via git\&. This inverts the default \fBpush\fR architecture of ansible into a \fBpull\fR architecture, which has near\-limitless scaling potential\&.
|
||||
.sp
|
||||
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 +65,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\&.
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
'\" t
|
||||
.\" Title: ansible
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
|
||||
.\" Date: 05/29/2013
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 08/21/2013
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.2
|
||||
.\" Source: Ansible 1.2.3
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "05/29/2013" "Ansible 1\&.2" "System administration commands"
|
||||
.TH "ANSIBLE" "1" "08/21/2013" "Ansible 1\&.2\&.3" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
@ -25,7 +34,7 @@ ansible \- run a command somewhere else
|
||||
ansible <host\-pattern> [\-f forks] [\-m module_name] [\-a args]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&.
|
||||
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&.
|
||||
.SH "ARGUMENTS"
|
||||
.PP
|
||||
\fBhost\-pattern\fR
|
||||
@ -73,7 +82,7 @@ search path to load modules from\&. The default is
|
||||
\fI/usr/share/ansible\fR\&. This can also be set with the ANSIBLE_LIBRARY environment variable\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-a\fR \'\fIARGUMENTS\fR\', \fB\-\-args=\fR\'\fIARGUMENTS\fR\'
|
||||
\fB\-a\fR \*(Aq\fIARGUMENTS\fR\*(Aq, \fB\-\-args=\fR\*(Aq\fIARGUMENTS\fR\*(Aq
|
||||
.RS 4
|
||||
The
|
||||
\fIARGUMENTS\fR
|
||||
|
||||
@ -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
|
||||
|
||||
@ -144,17 +94,18 @@ filter_plugins = /usr/share/ansible_plugins/filter_plugins
|
||||
|
||||
[paramiko_connection]
|
||||
|
||||
# nothing to configure yet
|
||||
# uncomment this line to cause the paramiko connection plugin to not record new host
|
||||
# keys encountered. Increases performance on new host additions. Setting works independently of the
|
||||
# host key checking setting above.
|
||||
|
||||
#record_host_keys=False
|
||||
|
||||
[ssh_connection]
|
||||
|
||||
# if uncommented, sets the ansible ssh arguments to the following. Leaving off ControlPersist
|
||||
# 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
|
||||
|
||||
# the following makes ansible use scp if the connection type is ssh (default is sftp)
|
||||
# ssh arguments to use
|
||||
# Leaving off ControlPersist will result in poor performance, so use
|
||||
# paramiko on older platforms rather than removing it
|
||||
#ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||
|
||||
#scp_if_ssh=True
|
||||
|
||||
|
||||
@ -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.3'
|
||||
__author__ = 'Michael DeHaan'
|
||||
|
||||
@ -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)
|
||||
|
||||
########################################################################
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -108,7 +126,10 @@ DEFAULT_LOG_PATH = shell_expand_path(get_config(p, DEFAULTS, 'log_
|
||||
|
||||
ANSIBLE_NOCOWS = get_config(p, DEFAULTS, 'nocows', 'ANSIBLE_NOCOWS', None)
|
||||
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))
|
||||
PARAMIKO_RECORD_HOST_KEYS = get_config(p, 'paramiko_connection', 'record_host_keys', 'ANSIBLE_PARAMIKO_RECORD_HOST_KEYS', True, boolean=True)
|
||||
ZEROMQ_PORT = int(get_config(p, 'fireball_connection', '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
|
||||
|
||||
@ -447,13 +447,7 @@ class PlayBook(object):
|
||||
basedir = self.inventory.basedir()
|
||||
filename = "%s.retry" % os.path.basename(self.filename)
|
||||
filename = filename.replace(".yml","")
|
||||
|
||||
if not os.path.exists('/var/tmp/ansible'):
|
||||
try:
|
||||
os.makedirs('/var/tmp/ansible')
|
||||
except:
|
||||
pass
|
||||
filename = os.path.join('/var/tmp/ansible', filename)
|
||||
filename = os.path.join(os.path.expandvars('$HOME/'), filename)
|
||||
|
||||
try:
|
||||
fd = open(filename, 'w')
|
||||
|
||||
@ -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)
|
||||
@ -680,7 +700,16 @@ class Runner(object):
|
||||
cmd += ' && echo %s' % basetmp
|
||||
|
||||
result = self._low_level_exec_command(conn, cmd, None, sudoable=False)
|
||||
if result['rc'] != 0:
|
||||
raise errors.AnsibleError('could not create temporary directory: '
|
||||
'SSH exited with return code %d' % result['rc'])
|
||||
rc = utils.last_non_blank_line(result['stdout']).strip() + '/'
|
||||
# Catch any other failure conditions here; files should never be
|
||||
# written directly to /.
|
||||
if rc == '/':
|
||||
raise errors.AnsibleError('failed to resolve remote temporary '
|
||||
'directory from %s: `%s` returned '
|
||||
'empty string' % (basetmp, cmd))
|
||||
return rc
|
||||
|
||||
|
||||
@ -759,8 +788,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 +801,7 @@ class Runner(object):
|
||||
for worker in workers:
|
||||
worker.terminate()
|
||||
worker.join()
|
||||
|
||||
|
||||
results = []
|
||||
try:
|
||||
while not result_queue.empty():
|
||||
@ -832,7 +862,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 +880,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)
|
||||
|
||||
# *****************************************************
|
||||
|
||||
@ -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,28 @@ class Connection(object):
|
||||
SFTP_CONNECTION_CACHE.pop(cache_key, None)
|
||||
if self.sftp is not None:
|
||||
self.sftp.close()
|
||||
|
||||
if C.PARAMIKO_RECORD_HOST_KEYS and self._any_keys_added():
|
||||
|
||||
# add any new SSH host keys -- warning -- this could be slow
|
||||
lockfile = self.keyfile.replace("known_hosts",".known_hosts.lock")
|
||||
dirname = os.path.dirname(self.keyfile)
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@ -23,7 +23,9 @@ import pipes
|
||||
import random
|
||||
import select
|
||||
import fcntl
|
||||
import hmac
|
||||
import pwd
|
||||
from hashlib import sha1
|
||||
import ansible.constants as C
|
||||
from ansible.callbacks import vvv
|
||||
from ansible import errors
|
||||
@ -39,6 +41,8 @@ class Connection(object):
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.private_key_file = private_key_file
|
||||
self.cp_dir = utils.prepare_writeable_dir('$HOME/.ansible/cp',mode=0700)
|
||||
self.HASHED_KEY_MAGIC = "|1|"
|
||||
|
||||
def connect(self):
|
||||
''' connect to the remote host '''
|
||||
@ -52,8 +56,22 @@ class Connection(object):
|
||||
else:
|
||||
self.common_args += ["-o", "ControlMaster=auto",
|
||||
"-o", "ControlPersist=60s",
|
||||
"-o", "ControlPath=/tmp/ansible-ssh-%h-%p-%r"]
|
||||
self.common_args += ["-o", "StrictHostKeyChecking=no"]
|
||||
"-o", "ControlPath=%s/ansible-ssh-%%h-%%p-%%r" % self.cp_dir]
|
||||
|
||||
cp_in_use = False
|
||||
cp_path_set = False
|
||||
for arg in self.common_args:
|
||||
if arg.find("ControlPersist") != -1:
|
||||
cp_in_use = True
|
||||
if arg.find("ControlPath") != -1:
|
||||
cp_path_set = True
|
||||
|
||||
if cp_in_use and not cp_path_set:
|
||||
self.common_args += ["-o", "ControlPath=%s/ansible-ssh-%%h-%%p-%%r" % self.cp_dir]
|
||||
|
||||
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 +108,35 @@ 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 tokens[0].find(self.HASHED_KEY_MAGIC) == 0:
|
||||
# this is a hashed known host entry
|
||||
try:
|
||||
(kn_salt,kn_host) = tokens[0][len(self.HASHED_KEY_MAGIC):].split("|",2)
|
||||
hash = hmac.new(kn_salt.decode('base64'), digestmod=sha1)
|
||||
hash.update(host)
|
||||
if hash.digest() == kn_host.decode('base64'):
|
||||
return False
|
||||
except:
|
||||
# invalid hashed host key, skip it
|
||||
continue
|
||||
else:
|
||||
# standard host file entry
|
||||
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 +153,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 +216,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')
|
||||
|
||||
@ -176,18 +176,34 @@ def is_executable(path):
|
||||
or stat.S_IXGRP & os.stat(path)[stat.ST_MODE]
|
||||
or stat.S_IXOTH & os.stat(path)[stat.ST_MODE])
|
||||
|
||||
def prepare_writeable_dir(tree):
|
||||
def unfrackpath(path):
|
||||
'''
|
||||
returns a path that is free of symlinks, environment
|
||||
variables, relative path traversals and symbols (~)
|
||||
example:
|
||||
'$HOME/../../var/mail' becomes '/var/spool/mail'
|
||||
'''
|
||||
return os.path.normpath(os.path.realpath(os.path.expandvars(os.path.expanduser(path))))
|
||||
|
||||
def prepare_writeable_dir(tree,mode=0777):
|
||||
''' make sure a directory exists and is writeable '''
|
||||
|
||||
if tree != '/':
|
||||
tree = os.path.realpath(os.path.expanduser(tree))
|
||||
# modify the mode to ensure the owner at least
|
||||
# has read/write access to this directory
|
||||
mode |= 0700
|
||||
|
||||
# make sure the tree path is always expanded
|
||||
# and normalized and free of symlinks
|
||||
tree = unfrackpath(tree)
|
||||
|
||||
if not os.path.exists(tree):
|
||||
try:
|
||||
os.makedirs(tree)
|
||||
os.makedirs(tree, mode)
|
||||
except (IOError, OSError), e:
|
||||
exit("Could not make dir %s: %s" % (tree, e))
|
||||
if not os.access(tree, os.W_OK):
|
||||
exit("Cannot write to path %s" % tree)
|
||||
return tree
|
||||
|
||||
def path_dwim(basedir, given):
|
||||
'''
|
||||
|
||||
@ -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 = {}
|
||||
|
||||
@ -85,7 +85,7 @@ def main():
|
||||
module.exit_json(changed=False, name=name, state=state)
|
||||
|
||||
rc, out, err = module.run_command('%s summary | grep %s' % (MONIT, name))
|
||||
running = 'Running' in out
|
||||
running = 'running' in out.lower()
|
||||
|
||||
if running and (state == 'started' or state == 'monitored'):
|
||||
module.exit_json(changed=False, name=name, state=state)
|
||||
|
||||
@ -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'))
|
||||
|
||||
@ -1,3 +1,21 @@
|
||||
ansible (1.2.3) unstable; urgency=high
|
||||
|
||||
* 1.2.3 release
|
||||
|
||||
-- James Cammarata <jcammarata@ansibleworks.com> Wed, 21 August 2013 13:00:00 -0500
|
||||
|
||||
ansible (1.2.2) unstable; urgency=low
|
||||
|
||||
* 1.2.2 release
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Thu, 05 July 2013 21:45:00 -0500
|
||||
|
||||
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
|
||||
|
||||
@ -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.3
|
||||
|
||||
Group: Development/Libraries
|
||||
License: GPLv3
|
||||
@ -116,6 +116,15 @@ rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
%changelog
|
||||
|
||||
* Wed Aug 21 2013 James Cammarata <jcammarata@ansibleworks.com> - 1.2-3
|
||||
* Release 1.2.3
|
||||
|
||||
* Thu Jul 05 2013 Michael DeHaan <michael.dehaan@gmail.com> - 1.2-2
|
||||
* Release 1.2.2
|
||||
|
||||
* 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user