developing_modules.rst 16.7 KB
Newer Older
Michael DeHaan committed
1
Developing Modules
2
==================
3

4 5
.. contents:: Topics

6
Ansible modules are reusable units of magic that can be used by the Ansible API,
7 8
or by the `ansible` or `ansible-playbook` programs.

Michael DeHaan committed
9 10
See :doc:`modules` for a list of various ones developed in core.

11
Modules can be written in any language and are found in the path specified
12
by `ANSIBLE_LIBRARY` or the ``--module-path`` command line option.
13

Michael DeHaan committed
14 15 16 17
Should you develop an interesting Ansible module, consider sending a pull request to the
`github project <http://github.com/ansible/ansible>`_ to see about getting your module
included in the core project.

18 19
.. _module_dev_tutorial:

20
Tutorial
21
````````
22

Michael DeHaan committed
23
Let's build a very-basic module to get and set the system time.  For starters, let's build
24
a module that just outputs the current time.
25

26
We are going to use Python here but any language is possible.  Only File I/O and outputting to standard
27
out are required.  So, bash, C++, clojure, Python, Ruby, whatever you want
28
is fine.
29

30 31 32
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
33
way later.
34

35 36
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.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

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.

Ok, let's get going with an example.  We'll use Python.  For starters, save this as a file named `time`::

    #!/usr/bin/python

    import datetime
    import json

    date = str(datetime.datetime.now())
    print json.dumps({
        "time" : date
    })

55 56
.. _module_testing:

57 58 59 60 61 62
Testing Modules
```````````````

There's a useful test script in the source checkout for ansible::

    git clone git@github.com:ansible/ansible.git
63
    source ansible/hacking/env-setup
64 65 66 67
    chmod +x ansible/hacking/test-module

Let's run the script you just wrote with that::

68
    ansible/hacking/test-module -m ./time
69 70 71 72 73

You should see output that looks something like this::

    {u'time': u'2012-03-14 22:13:48.539183'}

74
If you did not, you might have a typo in your module, so recheck it and try again.
75

76 77
.. _reading_input:

78 79 80 81 82 83
Reading Input
`````````````

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.

Martijn Koster committed
84
Ansible internally saves arguments to an arguments file.  So we must read the file
85 86 87 88 89 90 91 92 93
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.

94
.. note::
95 96 97 98
   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
99
is highly verbose because it's intended as an educational example.  You can write modules
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
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
121

122 123 124 125
    arguments = shlex.split(args_data)
    for arg in arguments:

        # ignore any arguments without an equals in it
126
        if "=" in arg:
127

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
            (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
162
                # data are returned, but it's always a
163 164
                # good idea to include whether or not
                # a change was made, as that will allow
165
                # notifiers to be used in playbooks.
166 167 168 169 170 171 172 173

                date = str(datetime.datetime.now())
                print json.dumps({
                    "time" : date,
                    "changed" : True
                })
                sys.exit(0)

174
    # if no parameters are sent, the module may or
175 176 177 178 179 180 181 182 183 184
    # 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::

185
    ansible/hacking/test-module -m ./time -a time=\"March 14 12:23\"
186 187 188

This should return something like::

Trevor Wennblom committed
189
    {"changed": true, "time": "2012-03-14 12:23:00.000307"}
Michael DeHaan committed
190

191 192
.. _module_provided_facts:

Michael DeHaan committed
193 194 195 196 197
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
198
this, just have the module return a `ansible_facts` key, like so, along with other return data::
Michael DeHaan committed
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214

    {
        "changed" : True,
        "rc" : 5,
        "ansible_facts" : {
            "leptons" : 5000
            "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.
215

216 217
.. _common_module_boilerplate:

218 219 220 221 222 223 224
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.

225
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/tree/devel/library>`_ that come with Ansible.
226

227
The 'group' and 'user' modules are reasonably non-trivial and showcase what this looks like.
228 229 230

Key parts include always ending the module file with::

231
    from ansible.module_utils.basic import *
232 233 234 235 236 237 238 239 240 241 242 243
    main()

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'])
        )
    )
244

245 246
The AnsibleModule provides lots of common code for handling returns, parses your arguments
for you, and allows you to check inputs.
247

248 249 250 251 252 253
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)::

Michael DeHaan committed
254
    module.fail_json(msg="Something fatal happened")
255

256
There are also other useful functions in the module class, such as module.md5(path).  See
257 258 259 260 261 262 263 264 265
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.

266 267
.. _developing_for_check_mode:

268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
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.

294 295
.. _module_dev_pitfalls:

296 297
Common Pitfalls
```````````````
298 299 300 301

You should also never do this in a module::

    print "some status message"
302

303 304 305
Because the output is supposed to be valid JSON.  Except that's not quite true,
but we'll get to that later.

306 307 308 309
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.
310 311

If a module returns stderr or otherwise fails to produce valid JSON, the actual output
312
will still be shown in Ansible, but the command will not succeed.
313 314 315 316

Always use the hacking/test-module script when developing modules and it will warn
you about these kind of things.

317 318
.. _module_dev_conventions:

319 320
Conventions/Recommendations
```````````````````````````
321 322 323 324

As a reminder from the example code above, here are some basic conventions
and guidelines:

325
* If the module is addressing an object, the parameter for that object should be called 'name' whenever possible, or accept 'name' as an alias.
326

327
* If you have a company module that returns facts specific to your installations, a good name for this module is `site_facts`.
328

329
* 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.
330

331
* 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.
332

333
* Modules must be self-contained in one file to be auto-transferred by ansible.
334

335
* 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.
336

337 338 339
* Modules should return JSON or key=value results all on one line.  JSON is best if you can do JSON.  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.

* 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'.
340

341
* Return codes from modules are not actually not significant, but continue on with 0=success and non-zero=failure for reasons of future proofing.
342

343
* 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.
344

345 346
.. _module_dev_shorthand:

347
Shorthand Vs JSON
Michael DeHaan committed
348
`````````````````
349 350 351 352

To make it easier to write modules in bash and in cases where a JSON
module might not be available, it is acceptable for a module to return
key=value output all on one line, like this.   The Ansible parser
Michael DeHaan committed
353
will know what to do::
354

Michael DeHaan committed
355
    somekey=1 somevalue=2 rc=3 favcolor=red
356 357 358 359

If you're writing a module in Python or Ruby or whatever, though, returning
JSON is probably the simplest way to go.

360
.. _module_documenting:
361

362 363 364 365 366 367 368 369 370
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.

371 372
.. _module_doc_example:

373 374 375
Example
+++++++

376
See an example documentation string in the checkout under `examples/DOCUMENTATION.yml <https://github.com/ansible/ansible/blob/devel/examples/DOCUMENTATION.yml>`_.
377 378 379 380 381 382 383 384 385 386 387 388 389

Include it in your module file like this::

    #!/usr/bin/env python
    # Copyright header....

    DOCUMENTATION = '''
    ---
    module: modulename
    short_description: This is a sentence describing the module
    # ... snip ...
    '''

390 391 392
The ``description``, and ``notes`` fields 
support formatting with some special macros.  

Jan-Piet Mens committed
393 394 395 396 397
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
parameters; module names should be specifies as ``M(module)``.

398
Examples (which typically contain colons, quotes, etc.) are difficult
399
to format with YAML, so these must be
400 401 402 403 404 405 406
written in plain text in an ``EXAMPLES`` string within the module
like this::

    EXAMPLES = '''
    - action: modulename opt1=arg1 opt2=arg2
    '''

407 408
The EXAMPLES section, just like the documentation section, is required in
all module pull requests for new modules.
409

410 411
.. _module_dev_testing:

412 413 414 415 416 417 418 419 420 421 422 423
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.

424 425 426 427
.. tip::

    You can use ANSIBLE_KEEP_REMOTE_FILES=1 to prevent ansible from
    deleting the remote files so you can debug your module.
428

429 430
.. _module_contribution:

431 432 433
Getting Your Module Into Core
`````````````````````````````

434
High-quality modules with minimal dependencies
435
can be included in the core, but core modules (just due to the programming
436 437
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
438 439
the program.   Stop by the mailing list to inquire about requirements if you like, and submit
a github pull request to the main project.
440

441 442 443 444
.. seealso::

   :doc:`modules`
       Learn about available modules
445 446 447 448
   :doc:`developing_plugins`
       Learn about developing plugins
   :doc:`developing_api`
       Learn about the Python API for playbook and task execution
449
   `Github modules directory <https://github.com/ansible/ansible/tree/devel/library>`_
450
       Browse source of core modules
451 452
   `Mailing List <http://groups.google.com/group/ansible-devel>`_
       Development mailing list
453 454
   `irc.freenode.net <http://irc.freenode.net>`_
       #ansible IRC chat channel
455 456