2012-02-23 23:19:06 +01:00
#!/usr/bin/python
2012-08-03 03:20:43 +02:00
# -*- coding: utf-8 -*-
2012-02-23 23:19:06 +01:00
2012-02-29 01:08:09 +01:00
# (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/>.
2012-02-24 10:35:51 +01:00
2012-02-23 23:19:06 +01:00
import sys
import datetime
2012-02-24 10:35:51 +01:00
import traceback
2012-10-01 08:30:53 +02:00
import re
2012-03-14 05:39:18 +01:00
import shlex
import os
2012-07-25 00:52:52 +02:00
2012-09-28 21:55:49 +02:00
DOCUMENTATION = '''
- - -
module : command
2013-11-19 00:55:49 +01:00
version_added : historical
2012-09-28 21:55:49 +02:00
short_description : Executes a command on a remote node
description :
2012-11-21 18:49:30 +01:00
- The M ( command ) module takes the command name followed by a list of space - delimited arguments .
2012-09-28 21:55:49 +02:00
- 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
2014-01-27 21:56:52 +01:00
like C ( " < " ) , C ( " > " ) , C ( " | " ) , and C ( " & " ) will not work ( use the M ( shell )
module if you need these features ) .
2012-09-28 21:55:49 +02:00
options :
free_form :
description :
2014-04-03 23:34:11 +02:00
- the command module takes a free form command to run . There is no parameter actually named ' free form ' .
See the examples !
2012-09-28 21:55:49 +02:00
required : true
default : null
aliases : [ ]
creates :
description :
- a filename , when it already exists , this step will B ( not ) be run .
required : no
default : null
2012-10-03 04:32:17 +02:00
removes :
description :
- a filename , when it does not exist , this step will B ( not ) be run .
version_added : " 0.8 "
required : no
default : null
2012-09-28 21:55:49 +02:00
chdir :
description :
- cd into this directory before running the command
2013-11-28 03:23:03 +01:00
version_added : " 0.6 "
2012-09-28 21:55:49 +02:00
required : false
default : null
2012-11-08 14:56:16 +01:00
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 "
2013-12-02 10:54:15 +01:00
warn :
2014-08-22 21:33:57 +02:00
version_added : " 1.8 "
default : yes
2013-12-02 10:54:15 +01:00
description :
2014-08-22 22:12:32 +02:00
- if command warnings are on in ansible . cfg , do not warn about this particular line if set to no / false .
2013-12-02 10:54:15 +01:00
required : false
default : True
2012-09-28 21:55:49 +02:00
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 .
2013-06-14 11:53:43 +02:00
- " 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. "
2012-09-28 21:55:49 +02:00
author : Michael DeHaan
'''
2013-06-14 11:53:43 +02:00
EXAMPLES = '''
2014-06-24 13:45:49 +02:00
# Example from Ansible Playbooks.
2013-06-14 11:53:43 +02:00
- command : / sbin / shutdown - t now
2014-06-24 13:45:49 +02:00
# Run the command if the specified file does not exist.
2013-06-14 11:53:43 +02:00
- command : / usr / bin / make_database . sh arg1 arg2 creates = / path / to / database
2014-06-24 13:45:49 +02:00
# You can also use the 'args' form to provide the options. This command
# will change the working directory to somedir/ and will only run when
# /path/to/database doesn't exist.
- command : / usr / bin / make_database . sh arg1 arg2
args :
chdir : somedir /
creates : / path / to / database
2013-06-14 11:53:43 +02:00
'''
2014-07-22 03:40:58 +02:00
# This is a pretty complex regex, which functions as follows:
#
# 1. (^|\s)
# ^ look for a space or the beginning of the line
# 2. (creates|removes|chdir|executable|NO_LOG)=
# ^ look for a valid param, followed by an '='
# 3. (?P<quote>[\'"])?
# ^ look for an optional quote character, which can either be
# a single or double quote character, and store it for later
# 4. (.*?)
# ^ match everything in a non-greedy manner until...
# 5. (?(quote)(?<!\\)(?P=quote))((?<!\\)(?=\s)|$)
# ^ a non-escaped space or a non-escaped quote of the same kind
# that was matched in the first 'quote' is found, or the end of
# the line is reached
2013-12-02 10:54:15 +01:00
PARAM_REGEX = re . compile ( r ' (^| \ s)(creates|removes|chdir|executable|NO_LOG|warn)=(?P<quote>[ \' " ])?(.*?)(?(quote)(?<! \\ )(?P=quote))((?<! \\ )(?= \ s)|$) ' )
def check_command ( commandline ) :
arguments = { ' chown ' : ' owner ' , ' chmod ' : ' mode ' , ' chgrp ' : ' group ' ,
' ln ' : ' state=link ' , ' mkdir ' : ' state=directory ' ,
' rmdir ' : ' state=absent ' , ' rm ' : ' state=absent ' , ' touch ' : ' state=touch ' }
commands = { ' git ' : ' git ' , ' hg ' : ' hg ' , ' curl ' : ' get_url ' , ' wget ' : ' get_url ' ,
' svn ' : ' subversion ' , ' service ' : ' service ' ,
' mount ' : ' mount ' , ' rpm ' : ' yum ' , ' yum ' : ' yum ' , ' apt-get ' : ' apt-get ' ,
' tar ' : ' unarchive ' , ' unzip ' : ' unarchive ' , ' sed ' : ' template or lineinfile ' ,
' rsync ' : ' synchronize ' }
warnings = list ( )
command = os . path . basename ( commandline . split ( ) [ 0 ] )
if command in arguments :
warnings . append ( " Consider using file module with %s rather than running %s " % ( arguments [ command ] , command ) )
if command in commands :
warnings . append ( " Consider using %s module rather than running %s " % ( commands [ command ] , command ) )
return warnings
2014-07-22 03:40:58 +02:00
2012-07-25 00:52:52 +02:00
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 ' ]
2012-07-30 17:39:45 +02:00
chdir = module . params [ ' chdir ' ]
2012-11-08 14:56:16 +01:00
executable = module . params [ ' executable ' ]
2012-07-25 00:52:52 +02:00
args = module . params [ ' args ' ]
2013-02-28 23:38:52 +01:00
creates = module . params [ ' creates ' ]
removes = module . params [ ' removes ' ]
2013-12-02 10:54:15 +01:00
warn = module . params . get ( ' warn ' , True )
2012-07-25 00:52:52 +02:00
2012-08-17 03:40:52 +02:00
if args . strip ( ) == ' ' :
2012-10-30 10:36:11 +01:00
module . fail_json ( rc = 256 , msg = " no command given " )
2012-08-14 02:17:07 +02:00
2012-07-30 17:39:45 +02:00
if chdir :
2013-05-29 17:05:11 +02:00
os . chdir ( chdir )
2012-07-30 17:39:45 +02:00
2013-02-28 23:38:52 +01:00
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 ,
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 ,
changed = False ,
stderr = False ,
rc = 0
)
2013-12-02 10:54:15 +01:00
warnings = list ( )
if warn :
warnings = check_command ( args )
2012-07-25 00:52:52 +02:00
if not shell :
args = shlex . split ( args )
startd = datetime . datetime . now ( )
2014-03-10 22:11:24 +01:00
rc , out , err = module . run_command ( args , executable = executable , use_unsafe_shell = shell )
2012-07-25 00:52:52 +02:00
endd = datetime . datetime . now ( )
delta = endd - startd
if out is None :
2012-08-11 18:35:58 +02:00
out = ' '
2012-07-25 00:52:52 +02:00
if err is None :
2012-08-11 18:35:58 +02:00
err = ' '
2012-07-25 00:52:52 +02:00
module . exit_json (
2013-12-02 10:54:15 +01:00
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 ,
warnings = warnings
2012-07-25 00:52:52 +02:00
)
2013-12-02 21:11:23 +01:00
# import module snippets
from ansible . module_utils . basic import *
2014-07-29 21:16:47 +02:00
from ansible . module_utils . splitter import *
2012-07-25 00:52:52 +02:00
# only the command module should ever need to do this
# everything else should be simple key=value
class CommandModule ( AnsibleModule ) :
2012-08-01 03:23:34 +02:00
def _handle_aliases ( self ) :
2013-02-25 23:07:47 +01:00
return { }
2012-08-01 03:23:34 +02:00
def _check_invalid_arguments ( self ) :
pass
2012-07-25 00:52:52 +02:00
def _load_params ( self ) :
''' read the input and return a dictionary and the arguments string '''
2012-08-03 03:20:43 +02:00
args = MODULE_ARGS
2012-07-25 00:52:52 +02:00
params = { }
2014-07-21 18:20:49 +02:00
params [ ' chdir ' ] = None
params [ ' creates ' ] = None
params [ ' removes ' ] = None
params [ ' shell ' ] = False
2012-11-08 14:56:16 +01:00
params [ ' executable ' ] = None
2013-12-02 10:54:15 +01:00
params [ ' warn ' ] = True
2014-01-23 16:02:17 +01:00
if " #USE_SHELL " in args :
2012-08-12 00:39:09 +02:00
args = args . replace ( " #USE_SHELL " , " " )
params [ ' shell ' ] = True
2012-07-25 00:52:52 +02:00
2014-07-29 21:16:47 +02:00
items = split_args ( args )
2014-07-21 18:20:49 +02:00
for x in items :
2014-07-24 04:37:57 +02:00
quoted = x . startswith ( ' " ' ) and x . endswith ( ' " ' ) or x . startswith ( " ' " ) and x . endswith ( " ' " )
if ' = ' in x and not quoted :
2014-07-21 18:20:49 +02:00
# check to see if this is a special parameter for the command
k , v = x . split ( ' = ' , 1 )
2014-07-29 21:16:47 +02:00
v = unquote ( v )
2014-07-24 04:37:57 +02:00
# because we're not breaking out quotes in the shlex split
# above, the value of the k=v pair may still be quoted. If
# so, remove them.
if len ( v ) > 1 and ( v . startswith ( ' " ' ) and v . endswith ( ' " ' ) or v . startswith ( " ' " ) and v . endswith ( " ' " ) ) :
v = v [ 1 : - 1 ]
2014-07-21 18:20:49 +02:00
if k in ( ' creates ' , ' removes ' , ' chdir ' , ' executable ' , ' NO_LOG ' ) :
if k == " chdir " :
v = os . path . abspath ( 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 k == " executable " :
v = os . path . abspath ( 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 )
params [ k ] = v
2014-07-22 03:40:58 +02:00
# Remove any of the above k=v params from the args string
args = PARAM_REGEX . sub ( ' ' , args )
2014-07-24 04:37:57 +02:00
params [ ' args ' ] = args . strip ( )
2013-12-02 10:54:15 +01:00
2012-09-29 21:04:05 +02:00
return ( params , params [ ' args ' ] )
2012-07-25 00:52:52 +02:00
main ( )