286ff41167
relative pathing comes in handy on occasion, particularly when delegating to localhost and running some command out of your playbook repo. Making use of os.path.abspath will allow for the full path to chdir and executable to be discovered if not provided.
208 lines
6.6 KiB
Python
208 lines
6.6 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>, and others
|
|
#
|
|
# This file is part of Ansible
|
|
#
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import sys
|
|
import datetime
|
|
import traceback
|
|
import re
|
|
import shlex
|
|
import os
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: command
|
|
short_description: Executes a command on a remote node
|
|
description:
|
|
- The M(command) module takes the command name followed by a list of space-delimited arguments.
|
|
- The given command will be executed on all selected nodes. It will not be
|
|
processed through the shell, so variables like C($HOME) and operations
|
|
like C("<"), C(">"), C("|"), and C("&") will not work.
|
|
options:
|
|
free_form:
|
|
description:
|
|
- the command module takes a free form command to run
|
|
required: true
|
|
default: null
|
|
aliases: []
|
|
creates:
|
|
description:
|
|
- a filename, when it already exists, this step will B(not) be run.
|
|
required: no
|
|
default: null
|
|
removes:
|
|
description:
|
|
- a filename, when it does not exist, this step will B(not) be run.
|
|
version_added: "0.8"
|
|
required: no
|
|
default: null
|
|
chdir:
|
|
description:
|
|
- cd into this directory before running the command
|
|
version_added: "0.6"
|
|
required: false
|
|
default: null
|
|
executable:
|
|
description:
|
|
- change the shell used to execute the command. Should be an absolute path to the executable.
|
|
required: false
|
|
default: null
|
|
version_added: "0.9"
|
|
notes:
|
|
- If you want to run a command through the shell (say you are using C(<),
|
|
C(>), C(|), etc), you actually want the M(shell) module instead. The
|
|
M(command) module is much more secure as it's not affected by the user's
|
|
environment.
|
|
- " C(creates), C(removes), and C(chdir) can be specified after the command. For instance, if you only want to run a command if a certain file does not exist, use this."
|
|
author: Michael DeHaan
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
# Example from Ansible Playbooks
|
|
- command: /sbin/shutdown -t now
|
|
|
|
# Run the command if the specified file does not exist
|
|
- command: /usr/bin/make_database.sh arg1 arg2 creates=/path/to/database
|
|
'''
|
|
|
|
def main():
|
|
|
|
# the command module is the one ansible module that does not take key=value args
|
|
# hence don't copy this one if you are looking to build others!
|
|
module = CommandModule(argument_spec=dict())
|
|
|
|
shell = module.params['shell']
|
|
chdir = module.params['chdir']
|
|
executable = module.params['executable']
|
|
args = module.params['args']
|
|
creates = module.params['creates']
|
|
removes = module.params['removes']
|
|
|
|
if args.strip() == '':
|
|
module.fail_json(rc=256, msg="no command given")
|
|
|
|
if chdir:
|
|
os.chdir(chdir)
|
|
|
|
if creates:
|
|
# do not run the command if the line contains creates=filename
|
|
# and the filename already exists. This allows idempotence
|
|
# of command executions.
|
|
v = os.path.expanduser(creates)
|
|
if os.path.exists(v):
|
|
module.exit_json(
|
|
cmd=args,
|
|
stdout="skipped, since %s exists" % v,
|
|
skipped=True,
|
|
changed=False,
|
|
stderr=False,
|
|
rc=0
|
|
)
|
|
|
|
if removes:
|
|
# do not run the command if the line contains removes=filename
|
|
# and the filename does not exist. This allows idempotence
|
|
# of command executions.
|
|
v = os.path.expanduser(removes)
|
|
if not os.path.exists(v):
|
|
module.exit_json(
|
|
cmd=args,
|
|
stdout="skipped, since %s does not exist" % v,
|
|
skipped=True,
|
|
changed=False,
|
|
stderr=False,
|
|
rc=0
|
|
)
|
|
|
|
if not shell:
|
|
args = shlex.split(args)
|
|
startd = datetime.datetime.now()
|
|
|
|
rc, out, err = module.run_command(args, executable=executable)
|
|
|
|
endd = datetime.datetime.now()
|
|
delta = endd - startd
|
|
|
|
if out is None:
|
|
out = ''
|
|
if err is None:
|
|
err = ''
|
|
|
|
module.exit_json(
|
|
cmd = args,
|
|
stdout = out.rstrip("\r\n"),
|
|
stderr = err.rstrip("\r\n"),
|
|
rc = rc,
|
|
start = str(startd),
|
|
end = str(endd),
|
|
delta = str(delta),
|
|
changed = True
|
|
)
|
|
|
|
# include magic from lib/ansible/module_common.py
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
|
|
|
# only the command module should ever need to do this
|
|
# everything else should be simple key=value
|
|
|
|
class CommandModule(AnsibleModule):
|
|
|
|
def _handle_aliases(self):
|
|
return {}
|
|
|
|
def _check_invalid_arguments(self):
|
|
pass
|
|
|
|
def _load_params(self):
|
|
''' read the input and return a dictionary and the arguments string '''
|
|
args = MODULE_ARGS
|
|
params = {}
|
|
params['chdir'] = None
|
|
params['creates'] = None
|
|
params['removes'] = None
|
|
params['shell'] = False
|
|
params['executable'] = None
|
|
if args.find("#USE_SHELL") != -1:
|
|
args = args.replace("#USE_SHELL", "")
|
|
params['shell'] = True
|
|
|
|
r = re.compile(r'(^|\s)(creates|removes|chdir|executable)=(?P<quote>[\'"])?(.*?)(?(quote)(?<!\\)(?P=quote))((?<!\\)(?=\s)|$)')
|
|
for m in r.finditer(args):
|
|
v = m.group(4).replace("\\", "")
|
|
if m.group(2) == "creates":
|
|
params['creates'] = v
|
|
elif m.group(2) == "removes":
|
|
params['removes'] = v
|
|
elif m.group(2) == "chdir":
|
|
v = os.path.expanduser(v)
|
|
v = os.path.abspath(v)
|
|
if not (os.path.exists(v) and os.path.isdir(v)):
|
|
self.fail_json(rc=258, msg="cannot change to directory '%s': path does not exist" % v)
|
|
params['chdir'] = v
|
|
elif m.group(2) == "executable":
|
|
v = os.path.expanduser(v)
|
|
v = os.path.abspath(v)
|
|
if not (os.path.exists(v)):
|
|
self.fail_json(rc=258, msg="cannot use executable '%s': file does not exist" % v)
|
|
params['executable'] = v
|
|
args = r.sub("", args)
|
|
params['args'] = args
|
|
return (params, params['args'])
|
|
|
|
main()
|