3a409a457c
The option will be passed to the Popen object created and will be used to execute the command instead of the default shell.
204 lines
7.1 KiB
Python
Executable file
204 lines
7.1 KiB
Python
Executable file
#!/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 subprocess
|
|
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 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. As such, all
|
|
paths to commands must be fully qualified
|
|
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"
|
|
examples:
|
|
- code: "command: /sbin/shutdown -t now"
|
|
description: "Example from Ansible Playbooks"
|
|
- code: "command: /usr/bin/make_database.sh arg1 arg2 creates=/path/to/database"
|
|
description: "I(creates), I(removes), and I(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."
|
|
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.
|
|
author: Michael DeHaan
|
|
'''
|
|
|
|
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']
|
|
|
|
if args.strip() == '':
|
|
module.fail_json(rc=256, msg="no command given")
|
|
|
|
if chdir:
|
|
os.chdir(os.path.expanduser(chdir))
|
|
|
|
if not shell:
|
|
args = shlex.split(args)
|
|
startd = datetime.datetime.now()
|
|
|
|
try:
|
|
cmd = subprocess.Popen(args, executable=executable, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
out, err = cmd.communicate()
|
|
except (OSError, IOError), e:
|
|
module.fail_json(rc=e.errno, msg=str(e), cmd=args)
|
|
except:
|
|
module.fail_json(rc=257, msg=traceback.format_exc(), cmd=args)
|
|
|
|
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 = cmd.returncode,
|
|
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):
|
|
pass
|
|
|
|
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['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":
|
|
# 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(v)
|
|
if os.path.exists(v):
|
|
self.exit_json(
|
|
cmd=args,
|
|
stdout="skipped, since %s exists" % v,
|
|
skipped=True,
|
|
changed=False,
|
|
stderr=False,
|
|
rc=0
|
|
)
|
|
elif m.group(2) == "removes":
|
|
# do not run the command if the line contains removes=filename
|
|
# and the filename do not exists. This allows idempotence
|
|
# of command executions.
|
|
v = os.path.expanduser(v)
|
|
if not os.path.exists(v):
|
|
self.exit_json(
|
|
cmd=args,
|
|
stdout="skipped, since %s does not exist" % v,
|
|
skipped=True,
|
|
changed=False,
|
|
stderr=False,
|
|
rc=0
|
|
)
|
|
elif m.group(2) == "chdir":
|
|
v = os.path.expanduser(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)
|
|
elif v[0] != '/':
|
|
self.fail_json(rc=259, msg="the path for 'chdir' argument must be fully qualified")
|
|
params['chdir'] = v
|
|
elif m.group(2) == "executable":
|
|
v = os.path.expanduser(v)
|
|
if not (os.path.exists(v)):
|
|
self.fail_json(rc=258, msg="cannot use executable '%s': file does not exist" % v)
|
|
elif v[0] != '/':
|
|
self.fail_json(rc=259, msg="the path for 'executable' argument must be fully qualified")
|
|
params['executable'] = v
|
|
args = r.sub("", args)
|
|
params['args'] = args
|
|
return (params, params['args'])
|
|
|
|
main()
|