2013-09-30 01:10:28 +02:00
Developing Modules
2012-05-13 17:00:02 +02:00
==================
2012-03-14 17:50:40 +01:00
2013-12-26 20:32:01 +01:00
.. contents :: Topics
2012-10-12 21:17:00 +02:00
Ansible modules are reusable units of magic that can be used by the Ansible API,
2012-03-14 17:50:40 +01:00
or by the `ansible` or `ansible-playbook` programs.
2013-10-05 19:50:53 +02:00
See :doc: `modules` for a list of various ones developed in core.
2012-10-12 21:17:00 +02:00
Modules can be written in any language and are found in the path specified
2013-03-15 19:55:53 +01:00
by `ANSIBLE_LIBRARY` or the `` --module-path `` command line option.
2012-08-01 05:08:04 +02:00
2014-10-02 08:52:29 +02:00
By default, everything that ships with ansible is pulled from its source tree, but
2014-09-26 23:52:50 +02:00
additional paths can be added.
The directory "./library", alongside your top level playbooks, is also automatically
added as a search directory.
2013-10-05 19:50:53 +02:00
Should you develop an interesting Ansible module, consider sending a pull request to the
2014-10-09 02:55:49 +02:00
`modules-extras project <http://github.com/ansible/ansible-modules-extras> `_ . There's also a core
2014-09-26 23:52:50 +02:00
repo for more established and widely used modules. "Extras" modules may be promoted to core periodically,
but there's no fundamental difference in the end - both ship with ansible, all in one package, regardless
of how you acquire ansible.
2013-10-05 19:50:53 +02:00
2013-10-04 16:41:44 +02:00
.. _module_dev_tutorial:
2012-10-12 21:17:00 +02:00
Tutorial
2012-03-14 17:50:40 +01:00
`` ` ` ` ` ``
2013-10-04 16:41:44 +02:00
2013-10-05 19:50:53 +02:00
Let's build a very-basic module to get and set the system time. For starters, let's build
2012-10-12 21:17:00 +02:00
a module that just outputs the current time.
2012-03-14 17:50:40 +01:00
2013-10-06 03:19:19 +02:00
We are going to use Python here but any language is possible. Only File I/O and outputting to standard
2012-03-14 17:50:40 +01:00
out are required. So, bash, C++, clojure, Python, Ruby, whatever you want
2012-10-12 21:17:00 +02:00
is fine.
2012-04-27 07:39:15 +02:00
2012-08-01 05:08:04 +02:00
Now Python Ansible modules contain some extremely powerful shortcuts (that all the core modules use)
but first we are going to build a module the very hard way. The reason we do this is because modules
written in any language OTHER than Python are going to have to do exactly this. We'll show the easy
2012-10-12 21:17:00 +02:00
way later.
2012-08-01 05:08:04 +02:00
2012-04-27 07:39:15 +02:00
So, here's an example. You would never really need to build a module to set the system time,
the 'command' module could already be used to do this. Though we're going to make one.
2012-03-14 17:50:40 +01:00
Reading the modules that come with ansible (linked above) is a great way to learn how to write
modules. Keep in mind, though, that some modules in ansible's source tree are internalisms,
so look at `service` or `yum` , and don't stare too close into things like `async_wrapper` or
you'll turn to stone. Nobody ever executes async_wrapper directly.
2014-12-02 16:09:31 +01:00
Ok, let's get going with an example. We'll use Python. For starters, save this as a file named `timetest.py` ::
2012-03-14 17:50:40 +01:00
#!/usr/bin/python
import datetime
import json
date = str(datetime.datetime.now())
print json.dumps({
"time" : date
})
2013-10-04 16:41:44 +02:00
.. _module_testing:
2012-03-14 17:50:40 +01:00
Testing Modules
`` ` ` ` ` ` ` ` ` ` ` ` ``
There's a useful test script in the source checkout for ansible::
2014-09-26 23:52:50 +02:00
git clone git@github.com:ansible/ansible.git --recursive
2013-11-01 18:29:31 +01:00
source ansible/hacking/env-setup
2012-03-14 17:50:40 +01:00
chmod +x ansible/hacking/test-module
Let's run the script you just wrote with that::
2014-12-02 16:09:31 +01:00
ansible/hacking/test-module -m ./timetest.py
2012-03-14 17:50:40 +01:00
You should see output that looks something like this::
{u'time': u'2012-03-14 22:13:48.539183'}
2012-05-02 07:35:02 +02:00
If you did not, you might have a typo in your module, so recheck it and try again.
2012-03-14 17:50:40 +01:00
2013-10-04 16:41:44 +02:00
.. _reading_input:
2012-03-14 17:50:40 +01:00
Reading Input
`` ` ` ` ` ` ` ` ` ` ``
2014-09-26 23:52:50 +02:00
2012-03-14 17:50:40 +01:00
Let's modify the module to allow setting the current time. We'll do this by seeing
if a key value pair in the form `time=<string>` is passed in to the module.
2012-04-13 00:20:52 +02:00
Ansible internally saves arguments to an arguments file. So we must read the file
2012-03-14 17:50:40 +01:00
and parse it. The arguments file is just a string, so any form of arguments are legal.
Here we'll do some basic parsing to treat the input as key=value.
The example usage we are trying to achieve to set the time is::
time time="March 14 22:10"
If no time parameter is set, we'll just leave the time as is and return the current time.
2014-01-10 04:59:47 +01:00
.. note ::
2012-03-14 17:50:40 +01:00
This is obviously an unrealistic idea for a module. You'd most likely just
use the shell module. However, it probably makes a decent tutorial.
Let's look at the code. Read the comments as we'll explain as we go. Note that this
2012-05-02 07:35:02 +02:00
is highly verbose because it's intended as an educational example. You can write modules
2012-03-14 17:50:40 +01:00
a lot shorter than this::
#!/usr/bin/python
# import some python modules that we'll use. These are all
# available in Python's core
import datetime
import sys
import json
import os
import shlex
# read the argument string from the arguments file
args_file = sys.argv[1]
args_data = file(args_file).read()
# for this module, we're going to do key=value style arguments
# this is up to each module to decide what it wants, but all
# core modules besides 'command' and 'shell' take key=value
# so this is highly recommended
2012-10-12 21:17:00 +02:00
2012-03-14 17:50:40 +01:00
arguments = shlex.split(args_data)
for arg in arguments:
# ignore any arguments without an equals in it
2014-01-23 16:02:17 +01:00
if "=" in arg:
2012-10-12 21:17:00 +02:00
2012-03-14 17:50:40 +01:00
(key, value) = arg.split("=")
# if setting the time, the key 'time'
# will contain the value we want to set the time to
if key == "time":
# now we'll affect the change. Many modules
# will strive to be 'idempotent', meaning they
# will only make changes when the desired state
# expressed to the module does not match
# the current state. Look at 'service'
# or 'yum' in the main git tree for an example
# of how that might look.
rc = os.system("date -s \"%s\"" % value)
# always handle all possible errors
#
# when returning a failure, include 'failed'
# in the return data, and explain the failure
# in 'msg'. Both of these conventions are
# required however additional keys and values
# can be added.
if rc != 0:
print json.dumps({
"failed" : True,
"msg" : "failed setting the time"
})
sys.exit(1)
# when things do not fail, we do not
# have any restrictions on what kinds of
2012-10-12 21:17:00 +02:00
# data are returned, but it's always a
2012-03-14 17:50:40 +01:00
# good idea to include whether or not
# a change was made, as that will allow
2012-10-12 21:17:00 +02:00
# notifiers to be used in playbooks.
2012-03-14 17:50:40 +01:00
date = str(datetime.datetime.now())
print json.dumps({
"time" : date,
"changed" : True
})
sys.exit(0)
2012-10-12 21:17:00 +02:00
# if no parameters are sent, the module may or
2012-03-14 17:50:40 +01:00
# may not error out, this one will just
# return the time
date = str(datetime.datetime.now())
print json.dumps({
"time" : date
})
Let's test that module::
2012-08-07 22:36:03 +02:00
ansible/hacking/test-module -m ./time -a time=\"March 14 12:23\"
2012-03-14 17:50:40 +01:00
This should return something like::
2013-04-11 06:43:16 +02:00
{"changed": true, "time": "2012-03-14 12:23:00.000307"}
2012-05-10 07:33:19 +02:00
2013-10-04 16:41:44 +02:00
.. _module_provided_facts:
2012-05-10 07:33:19 +02:00
Module Provided 'Facts'
`` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ``
The 'setup' module that ships with Ansible provides many variables about a system that can be used in playbooks
and templates. However, it's possible to also add your own facts without modifying the system module. To do
2012-08-01 04:19:04 +02:00
this, just have the module return a `ansible_facts` key, like so, along with other return data::
2012-05-10 07:33:19 +02:00
{
"changed" : True,
"rc" : 5,
"ansible_facts" : {
2014-06-05 19:00:05 +02:00
"leptons" : 5000,
2012-05-10 07:33:19 +02:00
"colors" : {
"red" : "FF0000",
"white" : "FFFFFF"
}
}
}
These 'facts' will be available to all statements called after that module (but not before) in the playbook.
A good idea might be make a module called 'site_facts' and always call it at the top of each playbook, though
we're always open to improving the selection of core facts in Ansible as well.
2012-03-14 17:50:40 +01:00
2013-10-04 16:41:44 +02:00
.. _common_module_boilerplate:
2012-08-01 05:08:04 +02:00
Common Module Boilerplate
`` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ``
As mentioned, if you are writing a module in Python, there are some very powerful shortcuts you can use.
Modules are still transferred as one file, but an arguments file is no longer needed, so these are not
only shorter in terms of code, they are actually FASTER in terms of execution time.
2014-10-01 07:23:08 +02:00
Rather than mention these here, the best way to learn is to read some of the `source of the modules <https://github.com/ansible/ansible-modules-core> `_ that come with Ansible.
2012-08-01 05:08:04 +02:00
2013-10-06 03:19:19 +02:00
The 'group' and 'user' modules are reasonably non-trivial and showcase what this looks like.
2012-08-01 05:08:04 +02:00
Key parts include always ending the module file with::
2013-12-25 19:23:58 +01:00
from ansible.module_utils.basic import *
2015-06-23 23:44:58 +02:00
if __name__ == '__main__':
main()
2012-08-01 05:08:04 +02:00
And instantiating the module class like::
module = AnsibleModule(
argument_spec = dict(
state = dict(default='present', choices=['present', 'absent']),
name = dict(required=True),
enabled = dict(required=True, choices=BOOLEANS),
something = dict(aliases=['whatever'])
)
)
2012-03-14 17:50:40 +01:00
2012-08-01 05:08:04 +02:00
The AnsibleModule provides lots of common code for handling returns, parses your arguments
for you, and allows you to check inputs.
2012-03-14 17:50:40 +01:00
2012-08-01 05:08:04 +02:00
Successful returns are made like this::
module.exit_json(changed=True, something_else=12345)
And failures are just as simple (where 'msg' is a required parameter to explain the error)::
2012-10-04 05:27:43 +02:00
module.fail_json(msg="Something fatal happened")
2012-08-01 05:08:04 +02:00
2014-11-07 06:28:04 +01:00
There are also other useful functions in the module class, such as module.sha1(path). See
2012-08-01 05:08:04 +02:00
lib/ansible/module_common.py in the source checkout for implementation details.
Again, modules developed this way are best tested with the hacking/test-module script in the git
source checkout. Because of the magic involved, this is really the only way the scripts
can function outside of Ansible.
If submitting a module to ansible's core code, which we encourage, use of the AnsibleModule
class is required.
2013-10-04 16:41:44 +02:00
.. _developing_for_check_mode:
2013-03-17 02:43:05 +01:00
Check Mode
`` ` ` ` ` ` ` ``
.. versionadded :: 1.1
Modules may optionally support check mode. If the user runs Ansible in check
mode, the module should try to predict whether changes will occur.
For your module to support check mode, you must pass `` supports_check_mode=True ``
when instantiating the AnsibleModule object. The AnsibleModule.check_mode attribute
will evaluate to True when check mode is enabled. For example::
module = AnsibleModule(
argument_spec = dict(...),
supports_check_mode=True
)
if module.check_mode:
# Check if any changes would be made by don't actually make those changes
module.exit_json(changed=check_if_system_state_would_be_changed())
Remember that, as module developer, you are responsible for ensuring that no
system state is altered when the user enables check mode.
If your module does not support check mode, when the user runs Ansible in check
mode, your module will simply be skipped.
2013-10-04 16:41:44 +02:00
.. _module_dev_pitfalls:
2012-08-01 05:08:04 +02:00
Common Pitfalls
`` ` ` ` ` ` ` ` ` ` ` ` ``
2012-03-14 17:50:40 +01:00
You should also never do this in a module::
print "some status message"
2012-10-12 21:17:00 +02:00
2014-12-02 20:41:52 +01:00
Because the output is supposed to be valid JSON.
2012-03-14 17:50:40 +01:00
2012-05-02 15:53:29 +02:00
Modules must not output anything on standard error, because the system will merge
standard out with standard error and prevent the JSON from parsing. Capturing standard
error and returning it as a variable in the JSON on standard out is fine, and is, in fact,
how the command module is implemented.
2012-04-27 07:39:15 +02:00
If a module returns stderr or otherwise fails to produce valid JSON, the actual output
2012-05-02 15:53:29 +02:00
will still be shown in Ansible, but the command will not succeed.
2012-04-27 07:39:15 +02:00
Always use the hacking/test-module script when developing modules and it will warn
you about these kind of things.
2013-10-04 16:41:44 +02:00
.. _module_dev_conventions:
2012-11-24 22:55:56 +01:00
Conventions/Recommendations
`` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ``
2012-03-14 17:50:40 +01:00
As a reminder from the example code above, here are some basic conventions
and guidelines:
2012-08-01 05:08:04 +02:00
* If the module is addressing an object, the parameter for that object should be called 'name' whenever possible, or accept 'name' as an alias.
2012-03-14 17:50:40 +01:00
2012-10-12 21:17:00 +02:00
* If you have a company module that returns facts specific to your installations, a good name for this module is `site_facts` .
2012-03-14 17:50:40 +01:00
2012-08-01 05:08:04 +02:00
* Modules accepting boolean status should generally accept 'yes', 'no', 'true', 'false', or anything else a user may likely throw at them. The AnsibleModule common code supports this with "choices=BOOLEANS" and a module.boolean(value) casting function.
2012-03-14 17:50:40 +01:00
2012-08-01 05:08:04 +02:00
* Include a minimum of dependencies if possible. If there are dependencies, document them at the top of the module file, and have the module raise JSON error messages when the import fails.
2012-03-14 17:50:40 +01:00
2014-05-03 17:59:50 +02:00
* Modules must be self-contained in one file to be auto-transferred by ansible.
2012-03-14 17:50:40 +01:00
2012-08-01 05:08:04 +02:00
* If packaging modules in an RPM, they only need to be installed on the control machine and should be dropped into /usr/share/ansible. This is entirely optional and up to you.
2012-03-14 17:50:40 +01:00
2014-12-02 20:41:52 +01:00
* Modules should output valid JSON only. All return types must be hashes (dictionaries) although they can be nested. Lists or simple scalar values are not supported, though they can be trivially contained inside a dictionary.
2012-08-01 05:08:04 +02:00
* In the event of failure, a key of 'failed' should be included, along with a string explanation in 'msg'. Modules that raise tracebacks (stacktraces) are generally considered 'poor' modules, though Ansible can deal with these returns and will automatically convert anything unparseable into a failed result. If you are using the AnsibleModule common Python code, the 'failed' element will be included for you automatically when you call 'fail_json'.
2012-03-14 17:50:40 +01:00
2014-05-03 17:59:50 +02:00
* Return codes from modules are not actually not significant, but continue on with 0=success and non-zero=failure for reasons of future proofing.
2012-08-01 05:08:04 +02:00
2013-10-06 03:19:19 +02:00
* As results from many hosts will be aggregated at once, modules should return only relevant output. Returning the entire contents of a log file is generally bad form.
2012-03-14 17:50:40 +01:00
2013-10-04 16:41:44 +02:00
.. _module_documenting:
2012-03-14 17:50:40 +01:00
2012-10-12 21:17:00 +02:00
Documenting Your Module
`` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ``
All modules included in the CORE distribution must have a
`` DOCUMENTATION `` string. This string MUST be a valid YAML document
which conforms to the schema defined below. You may find it easier to
start writing your `` DOCUMENTATION `` string in an editor with YAML
syntax highlighting before you include it in your Python file.
2013-10-04 16:41:44 +02:00
.. _module_doc_example:
2012-10-12 21:17:00 +02:00
Example
+++++++
2013-12-25 23:44:01 +01:00
See an example documentation string in the checkout under `examples/DOCUMENTATION.yml <https://github.com/ansible/ansible/blob/devel/examples/DOCUMENTATION.yml> `_ .
2012-10-12 21:17:00 +02:00
Include it in your module file like this::
2015-06-09 03:05:20 +02:00
#!/usr/bin/python
2012-10-12 21:17:00 +02:00
# Copyright header....
DOCUMENTATION = '''
---
module: modulename
short_description: This is a sentence describing the module
# ... snip ...
'''
2013-12-25 19:23:58 +01:00
The `` description `` , and `` notes `` fields
support formatting with some special macros.
2012-11-21 18:49:30 +01:00
These formatting functions are `` U() `` , `` M() `` , `` I() `` , and `` C() ``
for URL, module, italic, and constant-width respectively. It is suggested
to use `` C() `` for file and option names, and `` I() `` when referencing
2014-10-23 22:39:09 +02:00
parameters; module names should be specified as `` M(module) `` .
2012-11-21 18:49:30 +01:00
2013-02-18 15:31:38 +01:00
Examples (which typically contain colons, quotes, etc.) are difficult
2013-06-14 11:53:43 +02:00
to format with YAML, so these must be
2013-02-18 15:31:38 +01:00
written in plain text in an `` EXAMPLES `` string within the module
like this::
EXAMPLES = '''
- action: modulename opt1=arg1 opt2=arg2
'''
2013-12-25 19:23:58 +01:00
The EXAMPLES section, just like the documentation section, is required in
all module pull requests for new modules.
2013-02-18 15:31:38 +01:00
2013-10-04 16:41:44 +02:00
.. _module_dev_testing:
2012-10-12 21:17:00 +02:00
Building & Testing
++++++++++++++++++
Put your completed module file into the 'library' directory and then
run the command: `` make webdocs `` . The new 'modules.html' file will be
built and appear in the 'docsite/' directory.
.. tip ::
If you're having a problem with the syntax of your YAML you can
validate it on the `YAML Lint <http://www.yamllint.com/> `_ website.
2013-03-02 01:13:37 +01:00
.. tip ::
2014-10-01 16:54:48 +02:00
You can set the environment variable ANSIBLE_KEEP_REMOTE_FILES=1 on the controlling host to prevent ansible from
2013-03-02 01:13:37 +01:00
deleting the remote files so you can debug your module.
2012-10-12 21:17:00 +02:00
2013-10-04 16:41:44 +02:00
.. _module_contribution:
2014-09-26 23:52:50 +02:00
Module Paths
`` ` ` ` ` ` ` ` ` ``
If you are having trouble getting your module "found" by ansible, be sure it is in the ANSIBLE_LIBRARY_PATH.
If you have a fork of one of the ansible module projects, do something like this::
2014-10-05 12:35:22 +02:00
ANSIBLE_LIBRARY=~/ansible-modules-core:~/ansible-modules-extras
2014-09-26 23:52:50 +02:00
And this will make the items in your fork be loaded ahead of what ships with Ansible. Just be sure
to make sure you're not reporting bugs on versions from your fork!
To be safe, if you're working on a variant on something in Ansible's normal distribution, it's not
a bad idea to give it a new name while you are working on it, to be sure you know you're pulling
your version.
Getting Your Module Into Ansible
`` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ``
2012-03-14 17:50:40 +01:00
2012-10-12 21:17:00 +02:00
High-quality modules with minimal dependencies
2014-12-29 14:39:39 +01:00
can be included in Ansible, but modules (just due to the programming
2012-08-01 05:08:04 +02:00
preferences of the developers) will need to be implemented in Python and use
the AnsibleModule common code, and should generally use consistent arguments with the rest of
2013-10-04 16:41:44 +02:00
the program. Stop by the mailing list to inquire about requirements if you like, and submit
2014-09-26 23:52:50 +02:00
a github pull request to the `extras <https://github.com/ansible/ansible-modules-extras> `_ project.
2014-12-29 14:39:39 +01:00
Included modules will ship with ansible, and also have a chance to be promoted to 'core' status, which
2014-09-26 23:52:50 +02:00
gives them slightly higher development priority (though they'll work in exactly the same way).
2012-03-14 17:50:40 +01:00
2015-05-18 14:57:22 +02:00
Module checklist
`` ` ` ` ` ` ` ` ` ` ` ` ` ``
* The shebang should always be #!/usr/bin/python, this allows ansible_python_interpreter to work
* Documentation: Make sure it exists
2015-05-26 21:11:02 +02:00
* `required` should always be present, be it true or false
* If `required` is false you need to document `default` , even if its 'null'
* `default` is not needed for `required: true`
* Remove unnecessary doc like `aliases: []` or `choices: []`
* The version is not a float number and value the current development version
* The verify that arguments in doc and module spec dict are identical
* For password / secret arguments no_log=True should be set
* Requirements should be documented, using the `requirements=[]` field
* Author should be set, name and github id at least
* Made use of U() for urls, C() for files and options, I() for params, M() for modules?
* GPL License header
* Does module use check_mode? Could it be modified to use it? Document it
* Examples: make sure they are reproducible
* Return: document the return structure of the module
2015-05-18 14:57:22 +02:00
* Exceptions: The module must handle them. (exceptions are bugs)
* Give out useful messages on what you were doing and you can add the exception message to that.
* Avoid catchall exceptions, they are not very useful unless the underlying API gives very good error messages pertaining the attempted action.
* The module must not use sys.exit() --> use fail_json() from the module object
* Import custom packages in try/except and handled with fail_json() in main() e.g.::
2015-05-20 00:13:09 +02:00
try:
import foo
HAS_LIB=True
except:
HAS_LIB=False
2015-05-18 14:57:22 +02:00
2015-05-20 00:13:09 +02:00
* The return structure should be consistent, even if NA/None are used for keys normally returned under other options.
2015-05-18 14:57:22 +02:00
* Are module actions idempotent? If not document in the descriptions or the notes
* Import module snippets `from ansible.module_utils.basic import *` at the bottom, conserves line numbers for debugging.
2015-06-23 23:44:58 +02:00
* Call your :func: `main` from a condtional so that it would be possible to
test them in the future example::
if __name__ == '__main__':
main()
2015-05-18 14:57:22 +02:00
* Try to normalize parameters with other modules, you can have aliases for when user is more familiar with underlying API name for the option
* Being pep8 compliant is nice, but not a requirement. Specifically, the 80 column limit now hinders readability more that it improves it
* Avoid '`action` /`command` ', they are imperative and not declarative, there are other ways to express the same thing
* Sometimes you want to split the module, specially if you are adding a list/info state, you want a _facts version
* If you are asking 'how can i have a module execute other modules' ... you want to write a role
2014-10-28 16:43:18 +01:00
2014-10-29 15:32:17 +01:00
Deprecating and making module aliases
2014-10-28 16:43:18 +01:00
`` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ``
2014-12-04 23:23:35 +01:00
Starting in 1.8 you can deprecate modules by renaming them with a preceding _, i.e. old_cloud.py to
2014-10-28 16:43:18 +01:00
_old_cloud.py, This will keep the module available but hide it from the primary docs and listing.
You can also rename modules and keep an alias to the old name by using a symlink that starts with _.
This example allows the stat module to be called with fileinfo, making the following examples equivalent
EXAMPLES = '''
ln -s stat.py _fileinfo.py
ansible -m stat -a "path=/tmp" localhost
ansible -m fileinfo -a "path=/tmp" localhost
'''
2012-03-31 15:29:31 +02:00
.. seealso ::
:doc: `modules`
Learn about available modules
2013-10-05 18:31:16 +02:00
:doc: `developing_plugins`
Learn about developing plugins
:doc: `developing_api`
Learn about the Python API for playbook and task execution
2015-04-08 15:24:21 +02:00
`GitHub Core modules directory <https://github.com/ansible/ansible-modules-core/tree/devel> `_
2012-03-31 15:29:31 +02:00
Browse source of core modules
2015-04-08 15:24:21 +02:00
`Github Extras modules directory <https://github.com/ansible/ansible-modules-extras/tree/devel> `_
Browse source of extras modules.
2013-10-05 18:31:16 +02:00
`Mailing List <http://groups.google.com/group/ansible-devel> `_
Development mailing list
2012-03-31 15:55:37 +02:00
`irc.freenode.net <http://irc.freenode.net> `_
#ansible IRC chat channel
2013-10-05 18:31:16 +02:00