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

Added code to allow insertion of boilerplate into modules to make them able to share lots of code, the result

should be a huge reduction of total ansible source, at a slight cost of difficulty in original module development.

We need to apply this now to all modules, but may need to have some exemptions to things like command, which should
subclass this module.
This commit is contained in:
Michael DeHaan 2012-07-17 22:33:36 -04:00
parent ca21423c71
commit 9006d4557d
4 changed files with 154 additions and 53 deletions

View file

@ -31,6 +31,7 @@ import os
import subprocess import subprocess
import traceback import traceback
from ansible import utils from ansible import utils
from ansible import module_common
try: try:
import json import json
@ -54,6 +55,23 @@ argsfile = open(argspath, 'w')
argsfile.write(args) argsfile.write(args)
argsfile.close() argsfile.close()
module_fh = open(modfile)
module_data = module_fh.read()
included_boilerplate = module_data.find(module_common.REPLACER) != -1
module_fh.close()
if included_boilerplate:
module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON)
modfile2_path = os.path.expanduser("~/.ansible_module_generated")
print "* including generated source, if any, saving to: %s" % modfile2_path
print "* this will offset any line numbers in tracebacks!"
modfile2 = open(modfile2_path, 'w')
modfile2.write(module_data)
modfile2.close()
modfile = modfile2_path
else:
print "* module boilerplate substitution not requested in module, tracebacks will be unaltered"
os.system("chmod +x %s" % modfile) os.system("chmod +x %s" % modfile)
cmd = subprocess.Popen("%s %s" % (modfile, argspath), cmd = subprocess.Popen("%s %s" % (modfile, argspath),
shell=True, shell=True,

View file

@ -0,0 +1,114 @@
# (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/>.
REPLACER = "#<<INCLUDE_ANSIBLE_MODULE_COMMON>>"
MODULE_COMMON = """
# == BEGIN DYNAMICALLY INSERTED CODE ==
# ansible modules can be written in any language. To simplify
# development of Python modules, the functions available here
# can be inserted in any module source automatically by including
# #<<INCLUDE_ANSIBLE_MODULE_COMMON>> on a blank line by itself inside
# of an ansible module. The source of this common code lives
# in lib/ansible/module_common.py
try:
import json
except ImportError:
import simplejson as json
import os
import shlex
import subprocess
import sys
import syslog
class AnsibleModule(object):
def __init__(self, argument_spec, bypass_checks=False, no_log=False):
'''
@argument_spec: a hash of argument names, where the values are none if
the types are NOT checked, or a list of valid types where the argument
must be one of those values. All possible arguments must be listed.
@required_arguments: a list of arguments that must be sent to the module
'''
self.argument_spec = argument_spec
(self.params, self.args) = self._load_params()
if not bypass_checks:
self._check_required_arguments()
self._check_argument_types()
if not no_log:
self._log_invocation()
def _check_required_arguments(self):
''' ensure all required arguments are present '''
missing = []
for (k,v) in self.argument_spec.iteritems():
(type_spec, required) = v
if required and k not in self.params:
missing.append(k)
if len(missing) > 0:
self.fail_json(msg="missing required arguments: %s" % ",".join(missing))
def _check_argument_types(self):
''' ensure all arguments have the requested values, and there are no stray arguments '''
for (k,v) in self.argument_spec.iteritems():
(type_spec, required) = v
if type_spec is not None:
if type(spec) == list:
if v not in spec:
self.fail_json(msg="value of %s must be one of: %s, recieved: %s" % (k, ",".join(spec), v))
else:
self.fail_json(msg="internal error: do not know how to interpret argument_spec")
def _load_params(self):
''' read the input and return a dictionary and the arguments string '''
if len(sys.argv) == 2 and os.path.exists(sys.argv[1]):
argfile = sys.argv[1]
args = open(argfile, 'r').read()
else:
args = ' '.join(sys.argv[1:])
items = shlex.split(args)
params = {}
for x in items:
(k, v) = x.split("=")
params[k] = v
return (params, args)
def _log_invocation(self):
''' log that ansible ran the module '''
syslog.openlog('ansible-%s' % os.path.basename(__file__))
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % self.args)
def exit_json(self, rc=0, **kwargs):
''' return from the module, without error '''
kwargs['rc'] = rc
print json.dumps(kwargs)
sys.exit(rc)
def fail_json(self, **kwargs):
''' return from the module, with an error message '''
assert 'msg' in kwargs, "implementation error -- msg to explain the error is required"
kwargs['failed'] = True
self.exit_json(rc=1, **kwargs)
# == END DYNAMICALLY INSERTED CODE ===
"""

View file

@ -36,6 +36,7 @@ import ansible.constants as C
import ansible.inventory import ansible.inventory
from ansible import utils from ansible import utils
from ansible import errors from ansible import errors
from ansible import module_common
import poller import poller
import connection import connection
from ansible import callbacks as ans_callbacks from ansible import callbacks as ans_callbacks
@ -721,16 +722,20 @@ class Runner(object):
# use the correct python interpreter for the host # use the correct python interpreter for the host
host_variables = self.inventory.get_variables(conn.host) host_variables = self.inventory.get_variables(conn.host)
module_data = ""
with open(in_path) as f:
module_data = f.read()
module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON)
if 'ansible_python_interpreter' in host_variables: if 'ansible_python_interpreter' in host_variables:
interpreter = host_variables['ansible_python_interpreter'] interpreter = host_variables['ansible_python_interpreter']
with open(in_path) as f: module_lines = module_data.split('\n')
module_lines = f.readlines()
if '#!' and 'python' in module_lines[0]: if '#!' and 'python' in module_lines[0]:
module_lines[0] = "#!%s" % interpreter module_lines[0] = "#!%s" % interpreter
self._transfer_str(conn, tmp, module, '\n'.join(module_lines)) module_data = "\n".join(module_lines)
else:
conn.put_file(in_path, out_path)
self._transfer_str(conn, tmp, module, module_data)
return out_path return out_path
# ***************************************************** # *****************************************************

View file

@ -17,60 +17,24 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import shlex
import base64 import base64
import syslog
try: # this is magic, see lib/ansible/module_common.py
import json #<<INCLUDE_ANSIBLE_MODULE_COMMON>>
except ImportError:
import simplejson as json
# =========================================== module = AnsibleModule(
# convert arguments of form a=b c=d argument_spec = dict(
# to a dictionary src=(None,True),
)
)
source = module.params['src']
if len(sys.argv) == 1:
sys.exit(1)
argfile = sys.argv[1]
if not os.path.exists(argfile):
sys.exit(1)
args = open(argfile, 'r').read()
items = shlex.split(args)
syslog.openlog('ansible-%s' % os.path.basename(__file__))
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args)
params = {}
for x in items:
(k, v) = x.split("=")
params[k] = v
source = os.path.expanduser(params['src'])
# ==========================================
# raise an error if there is no template metadata
if not os.path.exists(source): if not os.path.exists(source):
print json.dumps(dict( module.fail_json(msg="file not found: %s" % source)
failed = 1,
msg = "file not found: %s" % source
))
sys.exit(1)
if not os.access(source, os.R_OK): if not os.access(source, os.R_OK):
print json.dumps(dict( module.fail_json(msg="file is not readable: %s" % source)
failed = 1,
msg = "file is not readable: %s" % source
))
sys.exit(1)
# ========================================== data = base64.b64encode(file(source).read())
data = file(source).read() module.exit_json(content=data, encoding='base64')
data = base64.b64encode(data)
print json.dumps(dict(content=data, encoding='base64'))
sys.exit(0)