1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00
community.general/lib/ansible/utils.py

283 lines
9 KiB
Python
Raw Normal View History

# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# 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-03-03 03:16:29 +01:00
###############################################################
import sys
import os
import shlex
import re
import jinja2
import yaml
try:
import json
except ImportError:
import simplejson as json
from ansible import errors
2012-03-03 03:16:29 +01:00
###############################################################
# UTILITY FUNCTIONS FOR COMMAND LINE TOOLS
###############################################################
def err(msg):
2012-03-03 03:16:29 +01:00
''' print an error message to stderr '''
print >> sys.stderr, msg
def exit(msg, rc=1):
2012-03-03 03:16:29 +01:00
''' quit with an error to stdout and a failure code '''
err(msg)
sys.exit(rc)
def bigjson(result):
2012-03-03 03:16:29 +01:00
''' format JSON output (uncompressed) '''
# hide some internals magic from command line userland
result2 = result.copy()
if 'invocation' in result2:
del result2['invocation']
return json.dumps(result2, sort_keys=True, indent=4)
def smjson(result):
2012-03-03 03:16:29 +01:00
''' format JSON output (compressed) '''
# hide some internals magic from command line userland
result2 = result.copy()
if 'invocation' in result2:
del result2['invocation']
return json.dumps(result2, sort_keys=True)
def task_start_msg(name, conditional):
if conditional:
return "\nNOTIFIED: [%s] **********\n" % name
else:
return "\nTASK: [%s] *********\n" % name
def regular_generic_msg(hostname, result, oneline, caption):
2012-03-03 03:16:29 +01:00
''' output on the result of a module run that is not command '''
if not oneline:
return "%s | %s >> %s\n" % (hostname, caption, bigjson(result))
else:
return "%s | %s >> %s\n" % (hostname, caption, smjson(result))
def regular_success_msg(hostname, result, oneline):
2012-03-03 03:16:29 +01:00
''' output the result of a successful module run '''
return regular_generic_msg(hostname, result, oneline, 'success')
def regular_failure_msg(hostname, result, oneline):
2012-03-03 03:16:29 +01:00
''' output the result of a failed module run '''
return regular_generic_msg(hostname, result, oneline, 'FAILED')
def command_generic_msg(hostname, result, oneline, caption):
2012-03-03 03:16:29 +01:00
''' output the result of a command run '''
rc = result.get('rc', '0')
stdout = result.get('stdout','')
stderr = result.get('stderr', '')
msg = result.get('msg', '')
if not oneline:
buf = "%s | %s | rc=%s >>\n" % (hostname, caption, result.get('rc',0))
if stdout:
buf += stdout
if stderr:
buf += stderr
if msg:
buf += msg
2012-03-10 19:40:08 +01:00
buf += "\n"
return buf
else:
if stderr:
2012-03-10 19:40:08 +01:00
return "%s | %s | rc=%s | (stdout) %s (stderr) %s\n" % (hostname, caption, rc, stdout, stderr)
else:
2012-03-10 19:40:08 +01:00
return "%s | %s | rc=%s | (stdout) %s\n" % (hostname, caption, rc, stdout)
def command_success_msg(hostname, result, oneline):
2012-03-03 03:16:29 +01:00
''' output from a successful command run '''
return command_generic_msg(hostname, result, oneline, 'success')
def command_failure_msg(hostname, result, oneline):
2012-03-03 03:16:29 +01:00
''' output from a failed command run '''
return command_generic_msg(hostname, result, oneline, 'FAILED')
def write_tree_file(tree, hostname, buf):
2012-03-03 03:16:29 +01:00
''' write something into treedir/hostname '''
# TODO: might be nice to append playbook runs per host in a similar way
# in which case, we'd want append mode.
path = os.path.join(tree, hostname)
fd = open(path, "w+")
fd.write(buf)
fd.close()
def is_failed(result):
2012-03-03 03:16:29 +01:00
''' is a given JSON result a failed result? '''
failed = False
rc = 0
if type(result) == dict:
failed = result.get('failed', 0)
rc = result.get('rc', 0)
if rc != 0:
return True
return failed
def host_report_msg(hostname, module_name, result, oneline):
2012-03-03 03:16:29 +01:00
''' summarize the JSON results for a particular host '''
buf = ''
failed = is_failed(result)
if module_name in [ 'command', 'shell' ] and 'ansible_job_id' not in result:
if not failed:
buf = command_success_msg(hostname, result, oneline)
else:
buf = command_failure_msg(hostname, result, oneline)
else:
if not failed:
buf = regular_success_msg(hostname, result, oneline)
else:
buf = regular_failure_msg(hostname, result, oneline)
return buf
def prepare_writeable_dir(tree):
2012-03-03 03:16:29 +01:00
''' make sure a directory exists and is writeable '''
if tree != '/':
tree = os.path.realpath(os.path.expanduser(tree))
if not os.path.exists(tree):
try:
os.makedirs(tree)
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)
def path_dwim(basedir, given):
''' make relative paths work like folks expect '''
if given.startswith("/"):
return given
elif given.startswith("~/"):
return os.path.expanduser(given)
else:
return os.path.join(basedir, given)
def async_poll_status(jid, host, clock, result):
if 'finished' in result:
return "<job %s> finished on %s" % (jid, host)
elif 'failed' in result:
return "<job %s> FAILED on %s" % (jid, host)
else:
return "<job %s> polling on %s, %s remaining" % (jid, host, clock)
def json_loads(data):
return json.loads(data)
def parse_json(data):
''' this version for module return data only '''
try:
return json.loads(data)
except:
# not JSON, but try "Baby JSON" which allows many of our modules to not
# require JSON and makes writing modules in bash much simpler
results = {}
tokens = shlex.split(data)
for t in tokens:
if t.find("=") == -1:
raise errors.AnsibleError("failed to parse: %s" % data)
(key,value) = t.split("=", 1)
if key == 'changed' or 'failed':
if value.lower() in [ 'true', '1' ]:
value = True
elif value.lower() in [ 'false', '0' ]:
value = False
if key == 'rc':
value = int(value)
results[key] = value
2012-03-16 02:53:14 +01:00
if len(results.keys()) == 0:
return { "failed" : True, "parsed" : False, "msg" : data }
return results
_KEYCRE = re.compile(r"\$(\w+)")
def varReplace(raw, vars):
'''Perform variable replacement of $vars
@param raw: String to perform substitution on.
@param vars: Dictionary of variables to replace. Key is variable name
(without $ prefix). Value is replacement string.
@return: Input raw string with substituted values.
'''
# this code originally from yum
done = [] # Completed chunks to return
while raw:
m = _KEYCRE.search(raw)
if not m:
done.append(raw)
break
# Determine replacement value (if unknown variable then preserve
# original)
varname = m.group(1).lower()
replacement = str(vars.get(varname, m.group()))
start, end = m.span()
done.append(raw[:start]) # Keep stuff leading up to token
done.append(replacement) # Append replacement value
raw = raw[end:] # Continue with remainder of string
return ''.join(done)
def template(text, vars):
''' run a text buffer through the templating engine '''
text = varReplace(str(text), vars)
template = jinja2.Template(text)
return template.render(vars)
def double_template(text, vars):
return template(template(text, vars), vars)
def template_from_file(path, vars):
''' run a file through the templating engine '''
data = file(path).read()
return template(data, vars)
def parse_yaml(data):
return yaml.load(data)
def parse_yaml_from_file(path):
try:
data = file(path).read()
except IOError:
raise errors.AnsibleError("file not found: %s" % path)
return parse_yaml(data)
def unquote_string(string):
''' remove single or double quotes from beginning/end of string'''
if (string.startswith('"') and string.endswith('"')) or \
(string.startswith("'") and string.endswith("'")):
return string[1:-1]
else:
return string
def parse_kv(args, unquote=True):
''' convert a string of key/value items to a dict '''
options = {}
for x in args:
if x.find("=") != -1:
k, v = x.split("=")
if unquote:
options[k]=unquote_string(v)
else:
v
return options