Added warnings to command module
Generate warnings when users are shelling out to commands rather than using modules Can be turned off on a per-action line with the documented warn=False flag. Can be turned off globally using command_warnings = False in ansible config file. Print out warnings using the standard playbook callbacks. Created some additional tests in TestRunner.test_command and also a demonstration playbook.
This commit is contained in:
parent
f54665aa51
commit
ab8490d003
5 changed files with 68 additions and 17 deletions
|
@ -535,6 +535,9 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
|
|||
display(msg, color='green', runner=self.runner)
|
||||
else:
|
||||
display(msg, color='yellow', runner=self.runner)
|
||||
if constants.COMMAND_WARNINGS and 'warnings' in host_result2 and host_result2['warnings']:
|
||||
for warning in host_result2['warnings']:
|
||||
display("warn: %s" % warning, color='purple', runner=self.runner)
|
||||
super(PlaybookRunnerCallbacks, self).on_ok(host, host_result)
|
||||
|
||||
def on_skipped(self, host, item=None):
|
||||
|
|
|
@ -170,6 +170,7 @@ HOST_KEY_CHECKING = get_config(p, DEFAULTS, 'host_key_checking', '
|
|||
SYSTEM_WARNINGS = get_config(p, DEFAULTS, 'system_warnings', 'ANSIBLE_SYSTEM_WARNINGS', True, boolean=True)
|
||||
DEPRECATION_WARNINGS = get_config(p, DEFAULTS, 'deprecation_warnings', 'ANSIBLE_DEPRECATION_WARNINGS', True, boolean=True)
|
||||
DEFAULT_CALLABLE_WHITELIST = get_config(p, DEFAULTS, 'callable_whitelist', 'ANSIBLE_CALLABLE_WHITELIST', [], islist=True)
|
||||
COMMAND_WARNINGS = get_config(p, DEFAULTS, 'command_warnings', 'ANSIBLE_COMMAND_WARNINGS', True, boolean=True)
|
||||
|
||||
# CONNECTION RELATED
|
||||
ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None)
|
||||
|
|
|
@ -67,6 +67,12 @@ options:
|
|||
required: false
|
||||
default: null
|
||||
version_added: "0.9"
|
||||
warn:
|
||||
description:
|
||||
- turn off warnings about running a command that is provided by an Ansible module.
|
||||
required: false
|
||||
default: True
|
||||
version_added: "1.5"
|
||||
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
|
||||
|
@ -108,7 +114,27 @@ EXAMPLES = '''
|
|||
# that was matched in the first 'quote' is found, or the end of
|
||||
# the line is reached
|
||||
|
||||
PARAM_REGEX = re.compile(r'(^|\s)(creates|removes|chdir|executable|NO_LOG)=(?P<quote>[\'"])?(.*?)(?(quote)(?<!\\)(?P=quote))((?<!\\)(?=\s)|$)')
|
||||
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',
|
||||
'echo': 'template or lineinfile', 'cp': 'synchronize or copy',
|
||||
'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
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
|
@ -122,6 +148,7 @@ def main():
|
|||
args = module.params['args']
|
||||
creates = module.params['creates']
|
||||
removes = module.params['removes']
|
||||
warn = module.params.get('warn', True)
|
||||
|
||||
if args.strip() == '':
|
||||
module.fail_json(rc=256, msg="no command given")
|
||||
|
@ -157,6 +184,10 @@ def main():
|
|||
rc=0
|
||||
)
|
||||
|
||||
warnings = list()
|
||||
if warn:
|
||||
warnings = check_command(args)
|
||||
|
||||
if not shell:
|
||||
args = shlex.split(args)
|
||||
startd = datetime.datetime.now()
|
||||
|
@ -172,14 +203,15 @@ def main():
|
|||
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
|
||||
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
|
||||
)
|
||||
|
||||
# import module snippets
|
||||
|
@ -206,6 +238,7 @@ class CommandModule(AnsibleModule):
|
|||
params['removes'] = None
|
||||
params['shell'] = False
|
||||
params['executable'] = None
|
||||
params['warn'] = True
|
||||
if "#USE_SHELL" in args:
|
||||
args = args.replace("#USE_SHELL", "")
|
||||
params['shell'] = True
|
||||
|
@ -236,6 +269,7 @@ class CommandModule(AnsibleModule):
|
|||
# Remove any of the above k=v params from the args string
|
||||
args = PARAM_REGEX.sub('', args)
|
||||
params['args'] = args.strip()
|
||||
|
||||
return (params, params['args'])
|
||||
|
||||
main()
|
||||
|
|
|
@ -41,6 +41,12 @@ options:
|
|||
required: false
|
||||
default: null
|
||||
version_added: "0.9"
|
||||
warn:
|
||||
description:
|
||||
- turn off warnings about running a command that is provided by an Ansible module.
|
||||
required: false
|
||||
default: True
|
||||
version_added: "1.5"
|
||||
notes:
|
||||
- If you want to execute a command securely and predictably, it may be
|
||||
better to use the M(command) module instead. Best practices when writing
|
||||
|
|
|
@ -115,6 +115,7 @@
|
|||
- "shell_result0.rc == 0"
|
||||
- "shell_result0.stderr == ''"
|
||||
- "shell_result0.stdout == 'win'"
|
||||
- "not shell_result0.warnings"
|
||||
|
||||
# executable
|
||||
|
||||
|
@ -143,6 +144,7 @@
|
|||
- "shell_result2.rc == 0"
|
||||
- "shell_result2.stderr == ''"
|
||||
- "shell_result2.stdout == 'win'"
|
||||
- "not shell_result2.warnings"
|
||||
|
||||
# creates
|
||||
|
||||
|
@ -157,17 +159,23 @@
|
|||
|
||||
# removes
|
||||
|
||||
- name: execute the test.sh script with chdir
|
||||
- name: remove afile.txt using rm
|
||||
shell: rm {{output_dir_test | expanduser}}/afile.txt removes={{output_dir_test | expanduser}}/afile.txt
|
||||
register: shell_result4
|
||||
|
||||
- name: assert that using rm under shell causes a warning
|
||||
assert:
|
||||
that:
|
||||
- "shell_result4.warnings"
|
||||
|
||||
- name: verify that afile.txt is absent
|
||||
file: path={{output_dir_test}}/afile.txt state=absent
|
||||
register: shell_result4
|
||||
register: shell_result5
|
||||
|
||||
- name: assert that the file was removed by the shell
|
||||
assert:
|
||||
that:
|
||||
- "shell_result4.changed == False"
|
||||
- "shell_result5.changed == False"
|
||||
|
||||
- name: execute a shell command using a literal multiline block
|
||||
args:
|
||||
|
@ -181,13 +189,12 @@
|
|||
| tr -s ' ' \
|
||||
| cut -f1 -d ' '
|
||||
echo "this is a second line"
|
||||
register: shell_result5
|
||||
register: shell_result6
|
||||
|
||||
- debug: var=shell_result5
|
||||
- debug: var=shell_result6
|
||||
|
||||
- name: assert the multiline shell command ran as expected
|
||||
assert:
|
||||
that:
|
||||
- "shell_result5.changed"
|
||||
- "shell_result5.stdout == '32f3cc201b69ed8afa3902b80f554ca8\nthis is a second line'"
|
||||
|
||||
- "shell_result6.changed"
|
||||
- "shell_result6.stdout == '32f3cc201b69ed8afa3902b80f554ca8\nthis is a second line'"
|
||||
|
|
Loading…
Reference in a new issue