diff --git a/.gitignore b/.gitignore index 3d33ca7a3a..52039a3033 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,9 @@ docs/man/man3/* *.sublime-project *.sublime-workspace # docsite stuff... -docsite/rst/modules +docsite/rst/modules_by_category.rst +docsite/rst/list_of_*.rst +docsite/rst/*_module.rst docsite/*.html docsite/_static/*.gif docsite/_static/*.png diff --git a/CHANGELOG.md b/CHANGELOG.md index a73f7e3253..f06f820a1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,40 @@ Ansible Changes By Release ## 1.5 "Love Walks In" - Release pending! +Major features/changes: + +* when_foo which was previously deprecated is now removed, use "when:" instead. Code generates appropriate error suggestion. +* include + with_items which was previously deprecated is now removed, ditto. Use with_nested / with_together, etc. +* only_if, which is much older than when_foo and was deprecated, is similarly removed. +* ssh connection plugin is now more efficient if you add 'pipelining=True' in ansible.cfg under [ssh_connection], see example.cfg + +New modules: + +* Details pending + +Misc: + * no_reboot is now defaulted to "no" in the ec2_ami module to ensure filesystem consistency in the resulting AMI. +* sysctl module overhauled +* authorized_key module overhauled +* synchronized module now handles local transport better +* apt_key module now ignores case on keys +* zypper_repository now skips on check mode +* file module now responds to force behavior when dealing with hardlinks +* new lookup plugin 'csvfile' +* fixes to allow hash_merge behavior to work with dynamic inventory +* mysql module will use port argument on dump/import +* subversion module now ignores locale to better intercept status messages +* rax api_key argument is no longer logged +* backwards/forwards compatibility for OpenStack modules, 'quantum' modules grok neutron renaming +* hosts properly uniqueified if appearing in redundant groups +* hostname module support added for ScientificLinux + +* various other bug fixes + +## 1.4.4 "Could This Be Magic" - January 6, 2014 + +- fixed a minor issue with newer versions of pip dropping the "use-mirrors" parameter. ## 1.4.3 "Could This Be Magic" - December 20, 2013 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe683efb06..b20e5f78aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,8 +32,10 @@ Sharing A Feature Idea If you have an idea for a new feature, you can open a new ticket at [github.com/ansible/ansible](https://github.com/ansible/ansible), though in general we like to talk about feature ideas first and bring in lots of people into the discussion. Consider stopping -by the [Ansible project mailing list](https://groups.google.com/forum/#!forum/ansible-project) or #ansible -on irc.freenode.net. +by the +[Ansible project mailing list](https://groups.google.com/forum/#!forum/ansible-project) ([Subscribe](https://groups.google.com/forum/#!forum/ansible-project/join)) +or #ansible on irc.freenode.net. There is an overview about more mailing lists +later in this document. Helping with Documentation -------------------------- diff --git a/Makefile b/Makefile index c617b1eec9..20f54855ab 100644 --- a/Makefile +++ b/Makefile @@ -64,9 +64,6 @@ all: clean python tests: PYTHONPATH=./lib ANSIBLE_LIBRARY=./library $(NOSETESTS) -d -v -# To force a rebuild of the docs run 'touch VERSION && make docs' -docs: $(MANPAGES) modulepages - authors: sh hacking/authors.sh @@ -127,7 +124,7 @@ install: sdist: clean docs $(PYTHON) setup.py sdist -t MANIFEST.in -rpmcommon: sdist +rpmcommon: $(MANPAGES) sdist @mkdir -p rpm-build @cp dist/*.gz rpm-build/ @sed -e 's#^Version:.*#Version: $(VERSION)#' -e 's#^Release:.*#Release: $(RPMRELEASE)%{?dist}#' $(RPMSPEC) >rpm-build/$(NAME).spec @@ -172,11 +169,6 @@ deb: debian # for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory -modulepages: - PYTHONPATH=./lib $(PYTHON) hacking/module_formatter.py -A $(VERSION) -t man -o docs/man/man3/ --module-dir=library --template-dir=hacking/templates # --verbose - -# because this requires Sphinx it is not run as part of every build, those building the RPM and so on can ignore this - -webdocs: +webdocs: $(MANPAGES) (cd docsite/; make docs) diff --git a/bin/ansible-galaxy b/bin/ansible-galaxy index 111ccd01e2..871e434b7d 100755 --- a/bin/ansible-galaxy +++ b/bin/ansible-galaxy @@ -88,7 +88,7 @@ A brief description of the role goes here. Requirements ------------ -Any pre-requisites that may not be covered by the ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. +Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. Role Variables -------------- @@ -135,8 +135,10 @@ def build_option_parser(action): the user wants to execute. """ - parser = OptionParser() - parser.set_usage("usage: %%prog [%s] [options] ..." % "|".join(VALID_ACTIONS)) + usage = "usage: %%prog [%s] [--help] [options] ..." % "|".join(VALID_ACTIONS) + epilog = "\nSee '%s --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0]) + OptionParser.format_epilog = lambda self, formatter: self.epilog + parser = OptionParser(usage=usage, epilog=epilog) if not action: parser.print_help() @@ -241,8 +243,12 @@ def api_lookup_role_by_name(api_server, role_name): role_name = urllib.quote(role_name) try: - user_name,role_name = role_name.split(".", 1) + parts = role_name.split(".") + user_name = ".".join(parts[0:-1]) + role_name = parts[-1] + print " downloading role '%s', owned by %s" % (role_name, user_name) except: + parser.print_help() print "Invalid role name (%s). You must specify username.rolename" % role_name sys.exit(1) @@ -481,7 +487,7 @@ def install_role(role_name, role_version, role_filename, options): # Action functions #------------------------------------------------------------------------------------- -def execute_init(args, options): +def execute_init(args, options, parser): """ Executes the init action, which creates the skeleton framework of a role that complies with the galaxy metadata format. @@ -513,6 +519,7 @@ def execute_init(args, options): "been modified there already." sys.exit(1) except Exception, e: + parser.print_help() print "No role name specified for init" sys.exit(1) @@ -574,7 +581,7 @@ def execute_init(args, options): f.close() print "%s was created successfully" % role_name -def execute_info(args, options): +def execute_info(args, options, parser): """ Executes the info action. This action prints out detailed information about an installed role as well as info available @@ -583,7 +590,7 @@ def execute_info(args, options): pass -def execute_install(args, options): +def execute_install(args, options, parser): """ Executes the installation action. The args list contains the roles to be installed, unless -f was specified. The list of roles @@ -598,11 +605,13 @@ def execute_install(args, options): if len(args) == 0 and not role_file: # the user needs to specify one of either --role-file # or specify a single user/role name + parser.print_help() print "You must specify a user/role name or a roles file" sys.exit() elif len(args) == 1 and role_file: # using a role file is mutually exclusive of specifying # the role name on the command line + parser.print_help() print "Please specify a user/role name, or a roles file, but not both" sys.exit(1) @@ -695,13 +704,14 @@ def execute_install(args, options): exit_without_ignore(options) sys.exit(0) -def execute_remove(args, options): +def execute_remove(args, options, parser): """ Executes the remove action. The args list contains the list of roles to be removed. This list can contain more than one role. """ if len(args) == 0: + parser.print_help() print 'You must specify at least one role to remove.' sys.exit() @@ -715,7 +725,7 @@ def execute_remove(args, options): print '%s is not installed, skipping.' % role sys.exit(0) -def execute_list(args, options): +def execute_list(args, options, parser): """ Executes the list action. The args list can contain zero or one role. If one is specified, only that role will be @@ -747,10 +757,12 @@ def execute_list(args, options): roles_path = get_opt(options, 'roles_path') roles_path = os.path.expanduser(roles_path) if not os.path.exists(roles_path): + parser.print_help() print "The path %s does not exist. Please specify a valid path with --roles-path" % roles_path sys.exit(1) elif not os.path.isdir(roles_path): print "%s exists, but it is not a directory. Please specify a valid path with --roles-path" % roles_path + parser.print_help() sys.exit(1) path_files = os.listdir(roles_path) for path_file in path_files: @@ -777,7 +789,7 @@ def main(): # execute the desired action if 1: #try: fn = globals()["execute_%s" % action] - fn(args, options) + fn(args, options, parser) #except KeyError, e: # print "Error: %s is not a valid action. Valid actions are: %s" % (action, ", ".join(VALID_ACTIONS)) # sys.exit(1) diff --git a/bin/ansible-playbook b/bin/ansible-playbook index 75bbbe054d..c0db66993c 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -270,4 +270,7 @@ if __name__ == "__main__": except errors.AnsibleError, e: display("ERROR: %s" % e, color='red', stderr=True) sys.exit(1) + except KeyboardInterrupt, ke: + display("ERROR: interrupted", color='red', stderr=True) + sys.exit(1) diff --git a/bin/ansible-pull b/bin/ansible-pull index 78f4d216e3..3253ced80c 100755 --- a/bin/ansible-pull +++ b/bin/ansible-pull @@ -45,23 +45,18 @@ import sys import datetime import socket from ansible import utils +from ansible.utils import cmd_functions DEFAULT_REPO_TYPE = 'git' DEFAULT_PLAYBOOK = 'local.yml' PLAYBOOK_ERRORS = {1: 'File does not exist', 2: 'File is not readable'} +VERBOSITY=0 -def _run(cmd): - print >>sys.stderr, "Running: '%s'" % cmd - cmd = subprocess.Popen(cmd, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - print out - if cmd.returncode != 0: - print >>sys.stderr, err - return cmd.returncode, out - +def increment_debug(option, opt, value, parser): + global VERBOSITY + VERBOSITY += 1 def try_playbook(path): if not os.path.exists(path): @@ -112,6 +107,8 @@ def main(args): 'not be updated') parser.add_option('-d', '--directory', dest='dest', default=None, help='directory to checkout repository to') + #parser.add_option('-l', '--live', default=True, action='store_live', + # help='Print the ansible-playbook output while running') parser.add_option('-U', '--url', dest='url', default=None, help='URL of the playbook repository') parser.add_option('-C', '--checkout', dest='checkout', @@ -119,6 +116,9 @@ def main(args): 'Defaults to behavior of repository module.') parser.add_option('-i', '--inventory-file', dest='inventory', help="location of the inventory host file") + parser.add_option('-v', '--verbose', default=False, action="callback", + callback=increment_debug, + help='Pass -vvvv to ansible-playbook') parser.add_option('-m', '--module-name', dest='module_name', default=DEFAULT_REPO_TYPE, help='Module name used to check out repository. ' @@ -141,8 +141,14 @@ def main(args): inv_opts = 'localhost,' limit_opts = 'localhost:%s:127.0.0.1' % hostname - base_opts = '-c local --limit "%s"' % limit_opts repo_opts = "name=%s dest=%s" % (options.url, options.dest) + + if VERBOSITY == 0: + base_opts = '-c local --limit "%s"' % limit_opts + elif VERBOSITY > 0: + debug_level = ''.join([ "v" for x in range(0, VERBOSITY) ]) + base_opts = '-%s -c local --limit "%s"' % (debug_level, limit_opts) + if options.checkout: repo_opts += ' version=%s' % options.checkout path = utils.plugins.module_finder.find_plugin(options.module_name) @@ -152,7 +158,10 @@ def main(args): cmd = 'ansible all -i "%s" %s -m %s -a "%s"' % ( inv_opts, base_opts, options.module_name, repo_opts ) - rc, out = _run(cmd) + + # RUN THE CHECKOUT COMMAND + rc, out, err = cmd_functions.run_cmd(cmd, live=True) + if rc != 0: if options.force: print "Unable to update repository. Continuing with (forced) run of playbook." @@ -172,7 +181,9 @@ def main(args): if options.inventory: cmd += ' -i "%s"' % options.inventory os.chdir(options.dest) - rc, out = _run(cmd) + + # RUN THE PLAYBOOK COMMAND + rc, out, err = cmd_functions.run_cmd(cmd, live=True) if options.purge: os.chdir('/') diff --git a/docs/man/man1/ansible-pull.1 b/docs/man/man1/ansible-pull.1 index 7644751d97..fb727631eb 100644 --- a/docs/man/man1/ansible-pull.1 +++ b/docs/man/man1/ansible-pull.1 @@ -1,13 +1,13 @@ '\" t .\" Title: ansible -.\" Author: [see the "AUTHOR" section] -.\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 11/27/2013 +.\" Author: :doctype:manpage +.\" Generator: DocBook XSL Stylesheets v1.76.1 +.\" Date: 01/02/2014 .\" Manual: System administration commands -.\" Source: Ansible 1.4.1 +.\" Source: Ansible 1.5 .\" Language: English .\" -.TH "ANSIBLE" "1" "11/27/2013" "Ansible 1\&.4\&.1" "System administration commands" +.TH "ANSIBLE" "1" "01/03/2014" "Ansible 1\&.5" "System administration commands" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- @@ -25,7 +25,7 @@ ansible-pull \- set up a remote copy of ansible on each managed node ansible \-d DEST \-U URL [options] [ ] .SH "DESCRIPTION" .sp -\fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&. +\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&. .sp Use ansible\-pull to set up a remote copy of ansible on each managed node, each set to run via cron and update playbook source via a source repository\&. This inverts the default \fBpush\fR architecture of ansible into a \fBpull\fR architecture, which has near\-limitless scaling potential\&. .sp @@ -77,6 +77,11 @@ Purge the checkout after the playbook is run\&. .RS 4 Module used to checkout playbook repository\&. Defaults to git\&. .RE +.PP +\fB\-o\fR, \fB\-\-only\-if\-changed\fR +.RS 4 +Run the playbook only if the repository has changed +.RE .SH "AUTHOR" .sp Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&. @@ -90,3 +95,9 @@ Ansible is released under the terms of the GPLv3 License\&. \fBansible\fR(1), \fBansible\-playbook\fR(1), \fBansible\-doc\fR(1) .sp Extensive documentation as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/ +.SH "AUTHOR" +.PP +\fB:doctype:manpage\fR +.RS 4 +Author. +.RE diff --git a/docs/man/man1/ansible-pull.1.asciidoc.in b/docs/man/man1/ansible-pull.1.asciidoc.in index 2e39d53aa8..12a38f674b 100644 --- a/docs/man/man1/ansible-pull.1.asciidoc.in +++ b/docs/man/man1/ansible-pull.1.asciidoc.in @@ -83,6 +83,9 @@ Purge the checkout after the playbook is run. Module used to checkout playbook repository. Defaults to git. +*-o*, *--only-if-changed*:: + +Run the playbook only if the repository has changed AUTHOR ------ diff --git a/docsite/.gitignore b/docsite/.gitignore index 7b47a7f7fd..e50d53255d 100644 --- a/docsite/.gitignore +++ b/docsite/.gitignore @@ -12,3 +12,4 @@ ansible*.xml objects.inv .doctrees rst/modules/*.rst +*.min.css diff --git a/docsite/Makefile b/docsite/Makefile index 56b84fbc88..feb8af9fbb 100644 --- a/docsite/Makefile +++ b/docsite/Makefile @@ -4,13 +4,13 @@ FORMATTER=../hacking/module_formatter.py all: clean docs -docs: clean modules +docs: clean modules staticmin ./build-site.py -viewdocs: clean +viewdocs: clean staticmin ./build-site.py view -htmldocs: +htmldocs: staticmin ./build-site.py rst clean: @@ -18,6 +18,8 @@ clean: -rm -f .buildinfo -rm -f *.inv -rm -rf *.doctrees + @echo "Cleaning up minified css files" + find . -type f -name "*.min.css" -delete @echo "Cleaning up byte compiled python stuff" find . -regex ".*\.py[co]$$" -delete @echo "Cleaning up editor backup files" @@ -27,6 +29,7 @@ clean: .PHONEY: docs clean modules: $(FORMATTER) ../hacking/templates/rst.j2 - PYTHONPATH=../lib $(FORMATTER) -t rst --template-dir=../hacking/templates --module-dir=../library -o rst/modules/ --includes-file=rst/modules/_list.rst - + PYTHONPATH=../lib $(FORMATTER) -t rst --template-dir=../hacking/templates --module-dir=../library -o rst/ +staticmin: + cat _themes/srtd/static/css/theme.css | sed -e 's/^[ \t]*//g; s/[ \t]*$$//g; s/\([:{;,]\) /\1/g; s/ {/{/g; s/\/\*.*\*\///g; /^$$/d' | sed -e :a -e '$$!N; s/\n\(.\)/\1/; ta' > _themes/srtd/static/css/theme.min.css diff --git a/docsite/README.md b/docsite/README.md index dd4f460805..bc6ee18139 100644 --- a/docsite/README.md +++ b/docsite/README.md @@ -5,12 +5,14 @@ This project hosts the source behind [ansibleworks.com/docs](http://www.ansiblew Contributions to the documentation are welcome. To make changes, submit a pull request that changes the reStructuredText files in the "rst/" directory only, and Michael can -do a docs build and push the static files. If you wish to verify output from the markup -such as link references, you may [install Sphinx] and build the documentation by running -`make viewdocs` from the `ansible/docsite` directory. To include module documentation -you'll need to run `make webdocs` at the top level of the repository. The generated -html files are in docsite/htmlout/ and really won't be formatted right until up -on ansibleworks.com. +do a docs build and push the static files. + +If you wish to verify output from the markup +such as link references, you may install sphinx and build the documentation by running +`make viewdocs` from the `ansible/docsite` directory. + +To include module documentation you'll need to run `make webdocs` at the top level of the repository. The generated +html files are in docsite/htmlout/. If you do not want to learn the reStructuredText format, you can also [file issues] about documentation problems on the Ansible GitHub project. @@ -19,7 +21,8 @@ Note that module documentation can actually be [generated from a DOCUMENTATION d in the modules directory, so corrections to modules written as such need to be made in the module source, rather than in docsite source. -[install Sphinx]: http://sphinx-doc.org/install.html +To install sphinx and the required theme, install pip and then "pip install sphinx sphinx_rtd_theme" + [file issues]: https://github.com/ansible/ansible/issues [module-docs]: http://www.ansibleworks.com/docs/developing_modules.html#documenting-your-module diff --git a/docsite/_static/solar.css b/docsite/_static/solar.css new file mode 100644 index 0000000000..15b5adef71 --- /dev/null +++ b/docsite/_static/solar.css @@ -0,0 +1,344 @@ +/* solar.css + * Modified from sphinxdoc.css of the sphinxdoc theme. +*/ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: 'Open Sans', sans-serif; + font-size: 14px; + line-height: 150%; + text-align: center; + color: #002b36; + padding: 0; + margin: 0px 80px 0px 80px; + min-width: 740px; + -moz-box-shadow: 0px 0px 10px #93a1a1; + -webkit-box-shadow: 0px 0px 10px #93a1a1; + box-shadow: 0px 0px 10px #93a1a1; + background: url("subtle_dots.png") repeat; + +} + +div.document { + background-color: #fcfcfc; + text-align: left; + background-repeat: repeat-x; +} + +div.bodywrapper { + margin: 0 240px 0 0; + border-right: 1px dotted #eee8d5; +} + +div.body { + background-color: white; + margin: 0; + padding: 0.5em 20px 20px 20px; +} + +div.related { + font-size: 1em; + background: #002b36; + color: #839496; + padding: 5px 0px; +} + +div.related ul { + height: 2em; + margin: 2px; +} + +div.related ul li { + margin: 0; + padding: 0; + height: 2em; + float: left; +} + +div.related ul li.right { + float: right; + margin-right: 5px; +} + +div.related ul li a { + margin: 0; + padding: 2px 5px; + line-height: 2em; + text-decoration: none; + color: #839496; +} + +div.related ul li a:hover { + background-color: #073642; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +div.sphinxsidebarwrapper { + padding: 0; +} + +div.sphinxsidebar { + margin: 0; + padding: 0.5em 15px 15px 0; + width: 210px; + float: right; + font-size: 0.9em; + text-align: left; +} + +div.sphinxsidebar h3, div.sphinxsidebar h4 { + margin: 1em 0 0.5em 0; + font-size: 1em; + padding: 0.7em; + background-color: #eeeff1; +} + +div.sphinxsidebar h3 a { + color: #2E3436; +} + +div.sphinxsidebar ul { + padding-left: 1.5em; + margin-top: 7px; + padding: 0; + line-height: 150%; + color: #586e75; +} + +div.sphinxsidebar ul ul { + margin-left: 20px; +} + +div.sphinxsidebar input { + border: 1px solid #eee8d5; +} + +div.footer { + background-color: #93a1a1; + color: #eee; + padding: 3px 8px 3px 0; + clear: both; + font-size: 0.8em; + text-align: right; +} + +div.footer a { + color: #eee; + text-decoration: none; +} + +/* -- body styles ----------------------------------------------------------- */ + +p { + margin: 0.8em 0 0.5em 0; +} + +div.body a, div.sphinxsidebarwrapper a { + color: #268bd2; + text-decoration: none; +} + +div.body a:hover, div.sphinxsidebarwrapper a:hover { + border-bottom: 1px solid #268bd2; +} + +h1, h2, h3, h4, h5, h6 { + font-family: "Open Sans", sans-serif; + font-weight: 300; +} + +h1 { + margin: 0; + padding: 0.7em 0 0.3em 0; + line-height: 1.2em; + color: #002b36; + text-shadow: #eee 0.1em 0.1em 0.1em; +} + +h2 { + margin: 1.3em 0 0.2em 0; + padding: 0 0 10px 0; + color: #073642; + border-bottom: 1px solid #eee; +} + +h3 { + margin: 1em 0 -0.3em 0; + padding-bottom: 5px; +} + +h3, h4, h5, h6 { + color: #073642; + border-bottom: 1px dotted #eee; +} + +div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { + color: #657B83!important; +} + +h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { + display: none; + margin: 0 0 0 0.3em; + padding: 0 0.2em 0 0.2em; + color: #aaa!important; +} + +h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, +h5:hover a.anchor, h6:hover a.anchor { + display: inline; +} + +h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, +h5 a.anchor:hover, h6 a.anchor:hover { + color: #777; + background-color: #eee; +} + +a.headerlink { + color: #c60f0f!important; + font-size: 1em; + margin-left: 6px; + padding: 0 4px 0 4px; + text-decoration: none!important; +} + +a.headerlink:hover { + background-color: #ccc; + color: white!important; +} + + +cite, code, tt { + font-family: 'Source Code Pro', monospace; + font-size: 0.9em; + letter-spacing: 0.01em; + background-color: #eeeff2; + font-style: normal; +} + +hr { + border: 1px solid #eee; + margin: 2em; +} + +.highlight { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +pre { + font-family: 'Source Code Pro', monospace; + font-style: normal; + font-size: 0.9em; + letter-spacing: 0.015em; + line-height: 120%; + padding: 0.7em; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +pre a { + color: inherit; + text-decoration: underline; +} + +td.linenos pre { + padding: 0.5em 0; +} + +div.quotebar { + background-color: #f8f8f8; + max-width: 250px; + float: right; + padding: 2px 7px; + border: 1px solid #ccc; +} + +div.topic { + background-color: #f8f8f8; +} + +table { + border-collapse: collapse; + margin: 0 -0.5em 0 -0.5em; +} + +table td, table th { + padding: 0.2em 0.5em 0.2em 0.5em; +} + +div.admonition { + font-size: 0.9em; + margin: 1em 0 1em 0; + border: 1px solid #eee; + background-color: #f7f7f7; + padding: 0; + -moz-box-shadow: 0px 8px 6px -8px #93a1a1; + -webkit-box-shadow: 0px 8px 6px -8px #93a1a1; + box-shadow: 0px 8px 6px -8px #93a1a1; +} + +div.admonition p { + margin: 0.5em 1em 0.5em 1em; + padding: 0.2em; +} + +div.admonition pre { + margin: 0.4em 1em 0.4em 1em; +} + +div.admonition p.admonition-title +{ + margin: 0; + padding: 0.2em 0 0.2em 0.6em; + color: white; + border-bottom: 1px solid #eee8d5; + font-weight: bold; + background-color: #268bd2; +} + +div.warning p.admonition-title, +div.important p.admonition-title { + background-color: #cb4b16; +} + +div.hint p.admonition-title, +div.tip p.admonition-title { + background-color: #859900; +} + +div.caution p.admonition-title, +div.attention p.admonition-title, +div.danger p.admonition-title, +div.error p.admonition-title { + background-color: #dc322f; +} + +div.admonition ul, div.admonition ol { + margin: 0.1em 0.5em 0.5em 3em; + padding: 0; +} + +div.versioninfo { + margin: 1em 0 0 0; + border: 1px solid #eee; + background-color: #DDEAF0; + padding: 8px; + line-height: 1.3em; + font-size: 0.9em; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; +} diff --git a/docsite/_static/solarized-dark.css b/docsite/_static/solarized-dark.css new file mode 100644 index 0000000000..6ebb945ce0 --- /dev/null +++ b/docsite/_static/solarized-dark.css @@ -0,0 +1,84 @@ +/* solarized dark style for solar theme */ + +/*style pre scrollbar*/ +pre::-webkit-scrollbar, .highlight::-webkit-scrollbar { + height: 0.5em; + background: #073642; +} + +pre::-webkit-scrollbar-thumb { + border-radius: 1em; + background: #93a1a1; +} + +/* pygments style */ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #002B36!important; color: #93A1A1 } +.highlight .c { color: #586E75 } /* Comment */ +.highlight .err { color: #93A1A1 } /* Error */ +.highlight .g { color: #93A1A1 } /* Generic */ +.highlight .k { color: #859900 } /* Keyword */ +.highlight .l { color: #93A1A1 } /* Literal */ +.highlight .n { color: #93A1A1 } /* Name */ +.highlight .o { color: #859900 } /* Operator */ +.highlight .x { color: #CB4B16 } /* Other */ +.highlight .p { color: #93A1A1 } /* Punctuation */ +.highlight .cm { color: #586E75 } /* Comment.Multiline */ +.highlight .cp { color: #859900 } /* Comment.Preproc */ +.highlight .c1 { color: #586E75 } /* Comment.Single */ +.highlight .cs { color: #859900 } /* Comment.Special */ +.highlight .gd { color: #2AA198 } /* Generic.Deleted */ +.highlight .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #DC322F } /* Generic.Error */ +.highlight .gh { color: #CB4B16 } /* Generic.Heading */ +.highlight .gi { color: #859900 } /* Generic.Inserted */ +.highlight .go { color: #93A1A1 } /* Generic.Output */ +.highlight .gp { color: #93A1A1 } /* Generic.Prompt */ +.highlight .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #CB4B16 } /* Generic.Subheading */ +.highlight .gt { color: #93A1A1 } /* Generic.Traceback */ +.highlight .kc { color: #CB4B16 } /* Keyword.Constant */ +.highlight .kd { color: #268BD2 } /* Keyword.Declaration */ +.highlight .kn { color: #859900 } /* Keyword.Namespace */ +.highlight .kp { color: #859900 } /* Keyword.Pseudo */ +.highlight .kr { color: #268BD2 } /* Keyword.Reserved */ +.highlight .kt { color: #DC322F } /* Keyword.Type */ +.highlight .ld { color: #93A1A1 } /* Literal.Date */ +.highlight .m { color: #2AA198 } /* Literal.Number */ +.highlight .s { color: #2AA198 } /* Literal.String */ +.highlight .na { color: #93A1A1 } /* Name.Attribute */ +.highlight .nb { color: #B58900 } /* Name.Builtin */ +.highlight .nc { color: #268BD2 } /* Name.Class */ +.highlight .no { color: #CB4B16 } /* Name.Constant */ +.highlight .nd { color: #268BD2 } /* Name.Decorator */ +.highlight .ni { color: #CB4B16 } /* Name.Entity */ +.highlight .ne { color: #CB4B16 } /* Name.Exception */ +.highlight .nf { color: #268BD2 } /* Name.Function */ +.highlight .nl { color: #93A1A1 } /* Name.Label */ +.highlight .nn { color: #93A1A1 } /* Name.Namespace */ +.highlight .nx { color: #93A1A1 } /* Name.Other */ +.highlight .py { color: #93A1A1 } /* Name.Property */ +.highlight .nt { color: #268BD2 } /* Name.Tag */ +.highlight .nv { color: #268BD2 } /* Name.Variable */ +.highlight .ow { color: #859900 } /* Operator.Word */ +.highlight .w { color: #93A1A1 } /* Text.Whitespace */ +.highlight .mf { color: #2AA198 } /* Literal.Number.Float */ +.highlight .mh { color: #2AA198 } /* Literal.Number.Hex */ +.highlight .mi { color: #2AA198 } /* Literal.Number.Integer */ +.highlight .mo { color: #2AA198 } /* Literal.Number.Oct */ +.highlight .sb { color: #586E75 } /* Literal.String.Backtick */ +.highlight .sc { color: #2AA198 } /* Literal.String.Char */ +.highlight .sd { color: #93A1A1 } /* Literal.String.Doc */ +.highlight .s2 { color: #2AA198 } /* Literal.String.Double */ +.highlight .se { color: #CB4B16 } /* Literal.String.Escape */ +.highlight .sh { color: #93A1A1 } /* Literal.String.Heredoc */ +.highlight .si { color: #2AA198 } /* Literal.String.Interpol */ +.highlight .sx { color: #2AA198 } /* Literal.String.Other */ +.highlight .sr { color: #DC322F } /* Literal.String.Regex */ +.highlight .s1 { color: #2AA198 } /* Literal.String.Single */ +.highlight .ss { color: #2AA198 } /* Literal.String.Symbol */ +.highlight .bp { color: #268BD2 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #268BD2 } /* Name.Variable.Class */ +.highlight .vg { color: #268BD2 } /* Name.Variable.Global */ +.highlight .vi { color: #268BD2 } /* Name.Variable.Instance */ +.highlight .il { color: #2AA198 } /* Literal.Number.Integer.Long */ diff --git a/docsite/_static/subtle_dots.png b/docsite/_static/subtle_dots.png new file mode 100644 index 0000000000..bb2d6117e5 Binary files /dev/null and b/docsite/_static/subtle_dots.png differ diff --git a/docsite/_themes/aworks/layout.html b/docsite/_themes/aworks/layout.html deleted file mode 100644 index 28068796da..0000000000 --- a/docsite/_themes/aworks/layout.html +++ /dev/null @@ -1,49 +0,0 @@ - - -
-
- -
-
- -{% macro navBar() %} - -{% endmacro %} - -{# Silence the sidebar's, relbar's #} -{% block sidebar1 %}{% endblock %} -{% block sidebar2 %}{% endblock %} -{% block relbar1 %}{% endblock %} -{% block relbar2 %}{% endblock %} - -{%- block content %} -
-
- - {% block header %}{{ navBar() }}{% endblock %} - - -
- {% block body %} {% endblock %} -
-
-
-{%- endblock %} - - diff --git a/docsite/_themes/aworks/relations.html b/docsite/_themes/aworks/relations.html deleted file mode 100644 index b3df68e3b5..0000000000 --- a/docsite/_themes/aworks/relations.html +++ /dev/null @@ -1,8 +0,0 @@ -{%- if prev %} -
  • {{ "«"|safe }} {{ prev.title }}
  • -{%- endif %} -{%- if next %} -
  • {{ next.title }} {{ "»"|safe }}
  • -{%- endif %} diff --git a/docsite/_themes/aworks/searchbox.html b/docsite/_themes/aworks/searchbox.html deleted file mode 100644 index 3063dde3d1..0000000000 --- a/docsite/_themes/aworks/searchbox.html +++ /dev/null @@ -1,7 +0,0 @@ -{%- if pagename != "search" %} -
    - - - -
    -{%- endif %} diff --git a/docsite/_themes/aworks/sourcelink.html b/docsite/_themes/aworks/sourcelink.html deleted file mode 100644 index 21ae2d8fef..0000000000 --- a/docsite/_themes/aworks/sourcelink.html +++ /dev/null @@ -1,4 +0,0 @@ -{%- if show_source and has_source and sourcename %} -
  • {{ _('Source') }}
  • -{%- endif %} diff --git a/docsite/_themes/aworks/theme.conf b/docsite/_themes/aworks/theme.conf deleted file mode 100644 index 64f9efd569..0000000000 --- a/docsite/_themes/aworks/theme.conf +++ /dev/null @@ -1,5 +0,0 @@ -# Twitter Bootstrap Theme -[theme] -inherit = basic -stylesheet = basic.css -pygments_style = tango diff --git a/docsite/_themes/bootstrap/globaltoc.html b/docsite/_themes/bootstrap/globaltoc.html deleted file mode 100644 index b5a5cb760f..0000000000 --- a/docsite/_themes/bootstrap/globaltoc.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/docsite/_themes/bootstrap/layout.html b/docsite/_themes/bootstrap/layout.html deleted file mode 100644 index 43431de062..0000000000 --- a/docsite/_themes/bootstrap/layout.html +++ /dev/null @@ -1,155 +0,0 @@ -{% extends "basic/layout.html" %} -{% set script_files = script_files + ['_static/bootstrap-dropdown.js', '_static/bootstrap-scrollspy.js'] %} -{% set css_files = ['_static/bootstrap.css', '_static/bootstrap-sphinx.css', '_static/ansible-local.css'] + css_files %} - -{# Sidebar: Rework into our Boostrap nav section. #} -{% macro navBar() %} -
    -
    -
    - - - -
    -
    -
    -{% endmacro %} -

    - -{%- block extrahead %} - - - - - -{% endblock %} - -{% block header %}{{ navBar() }}{% endblock %} - -{# Silence the sidebar's, relbar's #} -{% block sidebar1 %}{% endblock %} -{% block sidebar2 %}{% endblock %} -{% block relbar1 %}{% endblock %} -{% block relbar2 %}{% endblock %} - -{%- block content %} - -
    - {% block body %} {% endblock %} -
    -
    - -{%- endblock %} - -{%- block footer %} -
    -
    -AnsibleWorks -

    - {%- if show_copyright %} - {%- if hasdoc('copyright') %} - {% trans path=pathto('copyright'), copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
    - {%- else %} - {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
    - {%- endif %} - {%- endif %} - {%- if last_updated %} - {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
    - {%- endif %} -

    - -
    -
    -{%- endblock %} - diff --git a/docsite/_themes/bootstrap/localtoc.html b/docsite/_themes/bootstrap/localtoc.html deleted file mode 100644 index d3014cf7fa..0000000000 --- a/docsite/_themes/bootstrap/localtoc.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/docsite/_themes/bootstrap/relations.html b/docsite/_themes/bootstrap/relations.html deleted file mode 100644 index b3df68e3b5..0000000000 --- a/docsite/_themes/bootstrap/relations.html +++ /dev/null @@ -1,8 +0,0 @@ -{%- if prev %} -
  • {{ "«"|safe }} {{ prev.title }}
  • -{%- endif %} -{%- if next %} -
  • {{ next.title }} {{ "»"|safe }}
  • -{%- endif %} diff --git a/docsite/_themes/bootstrap/searchbox.html b/docsite/_themes/bootstrap/searchbox.html deleted file mode 100644 index 3063dde3d1..0000000000 --- a/docsite/_themes/bootstrap/searchbox.html +++ /dev/null @@ -1,7 +0,0 @@ -{%- if pagename != "search" %} -
    - - - -
    -{%- endif %} diff --git a/docsite/_themes/bootstrap/sourcelink.html b/docsite/_themes/bootstrap/sourcelink.html deleted file mode 100644 index 21ae2d8fef..0000000000 --- a/docsite/_themes/bootstrap/sourcelink.html +++ /dev/null @@ -1,4 +0,0 @@ -{%- if show_source and has_source and sourcename %} -
  • {{ _('Source') }}
  • -{%- endif %} diff --git a/docsite/_themes/bootstrap/static/bootstrap-dropdown.js b/docsite/_themes/bootstrap/static/bootstrap-dropdown.js deleted file mode 100644 index fda6da597e..0000000000 --- a/docsite/_themes/bootstrap/static/bootstrap-dropdown.js +++ /dev/null @@ -1,55 +0,0 @@ -/* ============================================================ - * bootstrap-dropdown.js v1.4.0 - * http://twitter.github.com/bootstrap/javascript.html#dropdown - * ============================================================ - * Copyright 2011 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function( $ ){ - - "use strict" - - /* DROPDOWN PLUGIN DEFINITION - * ========================== */ - - $.fn.dropdown = function ( selector ) { - return this.each(function () { - $(this).delegate(selector || d, 'click', function (e) { - var li = $(this).parent('li') - , isActive = li.hasClass('open') - - clearMenus() - !isActive && li.toggleClass('open') - return false - }) - }) - } - - /* APPLY TO STANDARD DROPDOWN ELEMENTS - * =================================== */ - - var d = 'a.menu, .dropdown-toggle' - - function clearMenus() { - $(d).parent('li').removeClass('open') - } - - $(function () { - $('html').bind("click", clearMenus) - $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' ) - }) - -}( window.jQuery || window.ender ); diff --git a/docsite/_themes/bootstrap/static/bootstrap-scrollspy.js b/docsite/_themes/bootstrap/static/bootstrap-scrollspy.js deleted file mode 100644 index efbc432960..0000000000 --- a/docsite/_themes/bootstrap/static/bootstrap-scrollspy.js +++ /dev/null @@ -1,107 +0,0 @@ -/* ============================================================= - * bootstrap-scrollspy.js v1.4.0 - * http://twitter.github.com/bootstrap/javascript.html#scrollspy - * ============================================================= - * Copyright 2011 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================== */ - - -!function ( $ ) { - - "use strict" - - var $window = $(window) - - function ScrollSpy( topbar, selector ) { - var processScroll = $.proxy(this.processScroll, this) - this.$topbar = $(topbar) - this.selector = selector || 'li > a' - this.refresh() - this.$topbar.delegate(this.selector, 'click', processScroll) - $window.scroll(processScroll) - this.processScroll() - } - - ScrollSpy.prototype = { - - refresh: function () { - this.targets = this.$topbar.find(this.selector).map(function () { - var href = $(this).attr('href') - return /^#\w/.test(href) && $(href).length ? href : null - }) - - this.offsets = $.map(this.targets, function (id) { - return $(id).offset().top - }) - } - - , processScroll: function () { - var scrollTop = $window.scrollTop() + 10 - , offsets = this.offsets - , targets = this.targets - , activeTarget = this.activeTarget - , i - - for (i = offsets.length; i--;) { - activeTarget != targets[i] - && scrollTop >= offsets[i] - && (!offsets[i + 1] || scrollTop <= offsets[i + 1]) - && this.activateButton( targets[i] ) - } - } - - , activateButton: function (target) { - this.activeTarget = target - - this.$topbar - .find(this.selector).parent('.active') - .removeClass('active') - - this.$topbar - .find(this.selector + '[href="' + target + '"]') - .parent('li') - .addClass('active') - } - - } - - /* SCROLLSPY PLUGIN DEFINITION - * =========================== */ - - $.fn.scrollSpy = function( options ) { - var scrollspy = this.data('scrollspy') - - if (!scrollspy) { - return this.each(function () { - $(this).data('scrollspy', new ScrollSpy( this, options )) - }) - } - - if ( options === true ) { - return scrollspy - } - - if ( typeof options == 'string' ) { - scrollspy[options]() - } - - return this - } - - $(document).ready(function () { - $('body').scrollSpy('[data-scrollspy] li > a') - }) - -}( window.jQuery || window.ender ); \ No newline at end of file diff --git a/docsite/_themes/bootstrap/static/bootstrap-sphinx.css_t b/docsite/_themes/bootstrap/static/bootstrap-sphinx.css_t deleted file mode 100644 index 044eb4f206..0000000000 --- a/docsite/_themes/bootstrap/static/bootstrap-sphinx.css_t +++ /dev/null @@ -1,24 +0,0 @@ -/* -* bootstrap-sphinx.css -* ~~~~~~~~~~~~~~~~~~~~ -* -* Sphinx stylesheet -- Twitter Bootstrap theme. -*/ - -body { - padding-top: 42px; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -{%- block sidebarlogo %} - {%- if logo %} -.topbar h3 a, .topbar .brand { - background: transparent url("{{ logo }}") no-repeat 22px 3px; - padding-left: 62px; -} - {%- endif %} -{%- endblock %} diff --git a/docsite/_themes/bootstrap/static/bootstrap.css b/docsite/_themes/bootstrap/static/bootstrap.css deleted file mode 100644 index 3ab0f1ff55..0000000000 --- a/docsite/_themes/bootstrap/static/bootstrap.css +++ /dev/null @@ -1,2469 +0,0 @@ -/*! - * Bootstrap v1.4.0 - * - * Copyright 2011 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - * Date: Sun Nov 20 21:42:29 PST 2011 - */ -/* Reset.less - * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here that cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc). - * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ -html, body { - margin: 0; - padding: 0; -} -h1, -h2, -h3, -h4, -h5, -h6, -p, -blockquote, -pre, -a, -abbr, -acronym, -address, -cite, -code, -del, -dfn, -em, -img, -q, -s, -samp, -small, -strike, -strong, -sub, -sup, -tt, -var, -dd, -dl, -dt, -li, -ol, -ul, -fieldset, -form, -label, -legend, -button, -table, -caption, -tbody, -tfoot, -thead, -tr, -th, -td { - margin: 0; - padding: 0; - border: 0; - font-weight: normal; - font-style: normal; - font-size: 100%; - line-height: 1; - font-family: inherit; -} -table { - border-collapse: collapse; - border-spacing: 0; -} -ol, ul { - list-style: none; -} -q:before, -q:after, -blockquote:before, -blockquote:after { - content: ""; -} -html { - overflow-y: scroll; - font-size: 100%; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} -a:focus { - outline: thin dotted; -} -a:hover, a:active { - outline: 0; -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -nav, -section { - display: block; -} -audio, canvas, video { - display: inline-block; - *display: inline; - *zoom: 1; -} -audio:not([controls]) { - display: none; -} -sub, sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} -sup { - top: -0.5em; -} -sub { - bottom: -0.25em; -} -img { - border: 0; - -ms-interpolation-mode: bicubic; -} -button, -input, -select, -textarea { - font-size: 100%; - margin: 0; - vertical-align: baseline; - *vertical-align: middle; -} -button, input { - line-height: normal; - *overflow: visible; -} -button::-moz-focus-inner, input::-moz-focus-inner { - border: 0; - padding: 0; -} -button, -input[type="button"], -input[type="reset"], -input[type="submit"] { - cursor: pointer; - -webkit-appearance: button; -} -input[type="search"] { - -webkit-appearance: textfield; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} -textarea { - overflow: auto; - vertical-align: top; -} -/* Variables.less - * Variables to customize the look and feel of Bootstrap - * ----------------------------------------------------- */ -/* Mixins.less - * Snippets of reusable CSS to develop faster and keep code readable - * ----------------------------------------------------------------- */ -/* - * Scaffolding - * Basic and global styles for generating a grid system, structural layout, and page templates - * ------------------------------------------------------------------------------------------- */ -body { - background-color: #ffffff; - margin: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - font-weight: normal; - line-height: 18px; - color: #404040; -} -.container { - width: 940px; - margin-left: auto; - margin-right: auto; - zoom: 1; -} -.container:before, .container:after { - display: table; - content: ""; - zoom: 1; -} -.container:after { - clear: both; -} -.container-fluid { - position: relative; - min-width: 940px; - padding-left: 20px; - padding-right: 20px; - zoom: 1; -} -.container-fluid:before, .container-fluid:after { - display: table; - content: ""; - zoom: 1; -} -.container-fluid:after { - clear: both; -} -.container-fluid > .sidebar { - position: absolute; - top: 0; - left: 20px; - width: 220px; -} -.container-fluid > .content { - margin-left: 240px; -} -a { - color: #0069d6; - text-decoration: none; - line-height: inherit; - font-weight: inherit; -} -a:hover { - color: #00438a; - text-decoration: underline; -} -.pull-right { - float: right; -} -.pull-left { - float: left; -} -.hide { - display: none; -} -.show { - display: block; -} -.row { - zoom: 1; - margin-left: -20px; -} -.row:before, .row:after { - display: table; - content: ""; - zoom: 1; -} -.row:after { - clear: both; -} -.row > [class*="span"] { - display: inline; - float: left; - margin-left: 20px; -} -.span1 { - width: 40px; -} -.span2 { - width: 100px; -} -.span3 { - width: 160px; -} -.span4 { - width: 220px; -} -.span5 { - width: 280px; -} -.span6 { - width: 340px; -} -.span7 { - width: 400px; -} -.span8 { - width: 460px; -} -.span9 { - width: 520px; -} -.span10 { - width: 580px; -} -.span11 { - width: 640px; -} -.span12 { - width: 700px; -} -.span13 { - width: 760px; -} -.span14 { - width: 820px; -} -.span15 { - width: 880px; -} -.span16 { - width: 940px; -} -.span17 { - width: 1000px; -} -.span18 { - width: 1060px; -} -.span19 { - width: 1120px; -} -.span20 { - width: 1180px; -} -.span21 { - width: 1240px; -} -.span22 { - width: 1300px; -} -.span23 { - width: 1360px; -} -.span24 { - width: 1420px; -} -.row > .offset1 { - margin-left: 80px; -} -.row > .offset2 { - margin-left: 140px; -} -.row > .offset3 { - margin-left: 200px; -} -.row > .offset4 { - margin-left: 260px; -} -.row > .offset5 { - margin-left: 320px; -} -.row > .offset6 { - margin-left: 380px; -} -.row > .offset7 { - margin-left: 440px; -} -.row > .offset8 { - margin-left: 500px; -} -.row > .offset9 { - margin-left: 560px; -} -.row > .offset10 { - margin-left: 620px; -} -.row > .offset11 { - margin-left: 680px; -} -.row > .offset12 { - margin-left: 740px; -} -.span-one-third { - width: 300px; -} -.span-two-thirds { - width: 620px; -} -.row > .offset-one-third { - margin-left: 340px; -} -.row > .offset-two-thirds { - margin-left: 660px; -} -/* Typography.less - * Headings, body text, lists, code, and more for a versatile and durable typography system - * ---------------------------------------------------------------------------------------- */ -p { - font-size: 13px; - font-weight: normal; - line-height: 18px; - margin-bottom: 9px; -} -p small { - font-size: 11px; - color: #bfbfbf; -} -h1, -h2, -h3, -h4, -h5, -h6 { - font-weight: bold; - color: #404040; -} -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small { - color: #bfbfbf; -} -h1 { - margin-bottom: 18px; - font-size: 30px; - line-height: 36px; -} -h1 small { - font-size: 18px; -} -h2 { - font-size: 24px; - line-height: 36px; -} -h2 small { - font-size: 14px; -} -h3, -h4, -h5, -h6 { - line-height: 36px; -} -h3 { - font-size: 18px; -} -h3 small { - font-size: 14px; -} -h4 { - font-size: 16px; -} -h4 small { - font-size: 12px; -} -h5 { - font-size: 14px; -} -h6 { - font-size: 13px; - color: #bfbfbf; - text-transform: uppercase; -} -ul, ol { - margin: 0 0 18px 25px; -} -ul ul, -ul ol, -ol ol, -ol ul { - margin-bottom: 0; -} -ul { - list-style: disc; -} -ol { - list-style: decimal; -} -li { - line-height: 18px; - color: #808080; -} -ul.unstyled { - list-style: none; - margin-left: 0; -} -dl { - margin-bottom: 18px; -} -dl dt, dl dd { - line-height: 18px; -} -dl dt { - font-weight: bold; -} -dl dd { - margin-left: 9px; -} -hr { - margin: 20px 0 19px; - border: 0; - border-bottom: 1px solid #eee; -} -strong { - font-style: inherit; - font-weight: bold; -} -em { - font-style: italic; - font-weight: inherit; - line-height: inherit; -} -.muted { - color: #bfbfbf; -} -blockquote { - margin-bottom: 18px; - border-left: 5px solid #eee; - padding-left: 15px; -} -blockquote p { - font-size: 14px; - font-weight: 300; - line-height: 18px; - margin-bottom: 0; -} -blockquote small { - display: block; - font-size: 12px; - font-weight: 300; - line-height: 18px; - color: #bfbfbf; -} -blockquote small:before { - content: '\2014 \00A0'; -} -address { - display: block; - line-height: 18px; - margin-bottom: 18px; -} -code, pre { - padding: 0 3px 2px; - font-family: Monaco, Andale Mono, Courier New, monospace; - font-size: 12px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} -.module { - font-family: Monaco, Andale Mono, Courier New, monospace; - font-size: 12px; -} -code { - color: rgba(0, 0, 0, 0.75); -} -pre { - background-color: #f5f5f5; - display: block; - padding: 8.5px; - margin: 0 0 18px; - line-height: 18px; - font-size: 12px; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.15); - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; -} -/* Forms.less - * Base styles for various input types, form layouts, and states - * ------------------------------------------------------------- */ -form { - margin-bottom: 18px; -} -fieldset { - margin-bottom: 18px; - padding-top: 18px; -} -fieldset legend { - display: block; - padding-left: 150px; - font-size: 19.5px; - line-height: 1; - color: #404040; - *padding: 0 0 5px 145px; - /* IE6-7 */ - - *line-height: 1.5; - /* IE6-7 */ - -} -form .clearfix { - margin-bottom: 18px; - zoom: 1; -} -form .clearfix:before, form .clearfix:after { - display: table; - content: ""; - zoom: 1; -} -form .clearfix:after { - clear: both; -} -label, -input, -select, -textarea { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - font-weight: normal; - line-height: normal; -} -label { - padding-top: 6px; - font-size: 13px; - line-height: 18px; - float: left; - width: 130px; - text-align: right; - color: #404040; -} -form .input { - margin-left: 150px; -} -input[type=checkbox], input[type=radio] { - cursor: pointer; -} -input, -textarea, -select, -.uneditable-input { - display: inline-block; - width: 210px; - height: 18px; - padding: 4px; - font-size: 13px; - line-height: 18px; - color: #808080; - border: 1px solid #ccc; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} -select { - padding: initial; -} -input[type=checkbox], input[type=radio] { - width: auto; - height: auto; - padding: 0; - margin: 3px 0; - *margin-top: 0; - /* IE6-7 */ - - line-height: normal; - border: none; -} -input[type=file] { - background-color: #ffffff; - padding: initial; - border: initial; - line-height: initial; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} -input[type=button], input[type=reset], input[type=submit] { - width: auto; - height: auto; -} -select, input[type=file] { - height: 27px; - *height: auto; - line-height: 27px; - *margin-top: 4px; - /* For IE7, add top margin to align select with labels */ - -} -select[multiple] { - height: inherit; - background-color: #ffffff; -} -textarea { - height: auto; -} -.uneditable-input { - background-color: #ffffff; - display: block; - border-color: #eee; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - cursor: not-allowed; -} -:-moz-placeholder { - color: #bfbfbf; -} -::-webkit-input-placeholder { - color: #bfbfbf; -} -input, textarea { - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-transition: border linear 0.2s, box-shadow linear 0.2s; - -ms-transition: border linear 0.2s, box-shadow linear 0.2s; - -o-transition: border linear 0.2s, box-shadow linear 0.2s; - transition: border linear 0.2s, box-shadow linear 0.2s; - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); - -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); -} -input:focus, textarea:focus { - outline: 0; - border-color: rgba(82, 168, 236, 0.8); - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); - -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); -} -input[type=file]:focus, input[type=checkbox]:focus, select:focus { - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - outline: 1px dotted #666; -} -form .clearfix.error > label, form .clearfix.error .help-block, form .clearfix.error .help-inline { - color: #b94a48; -} -form .clearfix.error input, form .clearfix.error textarea { - color: #b94a48; - border-color: #ee5f5b; -} -form .clearfix.error input:focus, form .clearfix.error textarea:focus { - border-color: #e9322d; - -webkit-box-shadow: 0 0 6px #f8b9b7; - -moz-box-shadow: 0 0 6px #f8b9b7; - box-shadow: 0 0 6px #f8b9b7; -} -form .clearfix.error .input-prepend .add-on, form .clearfix.error .input-append .add-on { - color: #b94a48; - background-color: #fce6e6; - border-color: #b94a48; -} -form .clearfix.warning > label, form .clearfix.warning .help-block, form .clearfix.warning .help-inline { - color: #c09853; -} -form .clearfix.warning input, form .clearfix.warning textarea { - color: #c09853; - border-color: #ccae64; -} -form .clearfix.warning input:focus, form .clearfix.warning textarea:focus { - border-color: #be9a3f; - -webkit-box-shadow: 0 0 6px #e5d6b1; - -moz-box-shadow: 0 0 6px #e5d6b1; - box-shadow: 0 0 6px #e5d6b1; -} -form .clearfix.warning .input-prepend .add-on, form .clearfix.warning .input-append .add-on { - color: #c09853; - background-color: #d2b877; - border-color: #c09853; -} -form .clearfix.success > label, form .clearfix.success .help-block, form .clearfix.success .help-inline { - color: #468847; -} -form .clearfix.success input, form .clearfix.success textarea { - color: #468847; - border-color: #57a957; -} -form .clearfix.success input:focus, form .clearfix.success textarea:focus { - border-color: #458845; - -webkit-box-shadow: 0 0 6px #9acc9a; - -moz-box-shadow: 0 0 6px #9acc9a; - box-shadow: 0 0 6px #9acc9a; -} -form .clearfix.success .input-prepend .add-on, form .clearfix.success .input-append .add-on { - color: #468847; - background-color: #bcddbc; - border-color: #468847; -} -.input-mini, -input.mini, -textarea.mini, -select.mini { - width: 60px; -} -.input-small, -input.small, -textarea.small, -select.small { - width: 90px; -} -.input-medium, -input.medium, -textarea.medium, -select.medium { - width: 150px; -} -.input-large, -input.large, -textarea.large, -select.large { - width: 210px; -} -.input-xlarge, -input.xlarge, -textarea.xlarge, -select.xlarge { - width: 270px; -} -.input-xxlarge, -input.xxlarge, -textarea.xxlarge, -select.xxlarge { - width: 530px; -} -textarea.xxlarge { - overflow-y: auto; -} -input.span1, textarea.span1 { - display: inline-block; - float: none; - width: 30px; - margin-left: 0; -} -input.span2, textarea.span2 { - display: inline-block; - float: none; - width: 90px; - margin-left: 0; -} -input.span3, textarea.span3 { - display: inline-block; - float: none; - width: 150px; - margin-left: 0; -} -input.span4, textarea.span4 { - display: inline-block; - float: none; - width: 210px; - margin-left: 0; -} -input.span5, textarea.span5 { - display: inline-block; - float: none; - width: 270px; - margin-left: 0; -} -input.span6, textarea.span6 { - display: inline-block; - float: none; - width: 330px; - margin-left: 0; -} -input.span7, textarea.span7 { - display: inline-block; - float: none; - width: 390px; - margin-left: 0; -} -input.span8, textarea.span8 { - display: inline-block; - float: none; - width: 450px; - margin-left: 0; -} -input.span9, textarea.span9 { - display: inline-block; - float: none; - width: 510px; - margin-left: 0; -} -input.span10, textarea.span10 { - display: inline-block; - float: none; - width: 570px; - margin-left: 0; -} -input.span11, textarea.span11 { - display: inline-block; - float: none; - width: 630px; - margin-left: 0; -} -input.span12, textarea.span12 { - display: inline-block; - float: none; - width: 690px; - margin-left: 0; -} -input.span13, textarea.span13 { - display: inline-block; - float: none; - width: 750px; - margin-left: 0; -} -input.span14, textarea.span14 { - display: inline-block; - float: none; - width: 810px; - margin-left: 0; -} -input.span15, textarea.span15 { - display: inline-block; - float: none; - width: 870px; - margin-left: 0; -} -input.span16, textarea.span16 { - display: inline-block; - float: none; - width: 930px; - margin-left: 0; -} -input[disabled], -select[disabled], -textarea[disabled], -input[readonly], -select[readonly], -textarea[readonly] { - background-color: #f5f5f5; - border-color: #ddd; - cursor: not-allowed; -} -.actions { - background: #f5f5f5; - margin-top: 18px; - margin-bottom: 18px; - padding: 17px 20px 18px 150px; - border-top: 1px solid #ddd; - -webkit-border-radius: 0 0 3px 3px; - -moz-border-radius: 0 0 3px 3px; - border-radius: 0 0 3px 3px; -} -.actions .secondary-action { - float: right; -} -.actions .secondary-action a { - line-height: 30px; -} -.actions .secondary-action a:hover { - text-decoration: underline; -} -.help-inline, .help-block { - font-size: 13px; - line-height: 18px; - color: #bfbfbf; -} -.help-inline { - padding-left: 5px; - *position: relative; - /* IE6-7 */ - - *top: -5px; - /* IE6-7 */ - -} -.help-block { - display: block; - max-width: 600px; -} -.inline-inputs { - color: #808080; -} -.inline-inputs span { - padding: 0 2px 0 1px; -} -.input-prepend input, .input-append input { - -webkit-border-radius: 0 3px 3px 0; - -moz-border-radius: 0 3px 3px 0; - border-radius: 0 3px 3px 0; -} -.input-prepend .add-on, .input-append .add-on { - position: relative; - background: #f5f5f5; - border: 1px solid #ccc; - z-index: 2; - float: left; - display: block; - width: auto; - min-width: 16px; - height: 18px; - padding: 4px 4px 4px 5px; - margin-right: -1px; - font-weight: normal; - line-height: 18px; - color: #bfbfbf; - text-align: center; - text-shadow: 0 1px 0 #ffffff; - -webkit-border-radius: 3px 0 0 3px; - -moz-border-radius: 3px 0 0 3px; - border-radius: 3px 0 0 3px; -} -.input-prepend .active, .input-append .active { - background: #a9dba9; - border-color: #46a546; -} -.input-prepend .add-on { - *margin-top: 1px; - /* IE6-7 */ - -} -.input-append input { - float: left; - -webkit-border-radius: 3px 0 0 3px; - -moz-border-radius: 3px 0 0 3px; - border-radius: 3px 0 0 3px; -} -.input-append .add-on { - -webkit-border-radius: 0 3px 3px 0; - -moz-border-radius: 0 3px 3px 0; - border-radius: 0 3px 3px 0; - margin-right: 0; - margin-left: -1px; -} -.inputs-list { - margin: 0 0 5px; - width: 100%; -} -.inputs-list li { - display: block; - padding: 0; - width: 100%; -} -.inputs-list label { - display: block; - float: none; - width: auto; - padding: 0; - margin-left: 20px; - line-height: 18px; - text-align: left; - white-space: normal; -} -.inputs-list label strong { - color: #808080; -} -.inputs-list label small { - font-size: 11px; - font-weight: normal; -} -.inputs-list .inputs-list { - margin-left: 25px; - margin-bottom: 10px; - padding-top: 0; -} -.inputs-list:first-child { - padding-top: 6px; -} -.inputs-list li + li { - padding-top: 2px; -} -.inputs-list input[type=radio], .inputs-list input[type=checkbox] { - margin-bottom: 0; - margin-left: -20px; - float: left; -} -.form-stacked { - padding-left: 20px; -} -.form-stacked fieldset { - padding-top: 9px; -} -.form-stacked legend { - padding-left: 0; -} -.form-stacked label { - display: block; - float: none; - width: auto; - font-weight: bold; - text-align: left; - line-height: 20px; - padding-top: 0; -} -.form-stacked .clearfix { - margin-bottom: 9px; -} -.form-stacked .clearfix div.input { - margin-left: 0; -} -.form-stacked .inputs-list { - margin-bottom: 0; -} -.form-stacked .inputs-list li { - padding-top: 0; -} -.form-stacked .inputs-list li label { - font-weight: normal; - padding-top: 0; -} -.form-stacked div.clearfix.error { - padding-top: 10px; - padding-bottom: 10px; - padding-left: 10px; - margin-top: 0; - margin-left: -10px; -} -.form-stacked .actions { - margin-left: -20px; - padding-left: 20px; -} -/* - * Tables.less - * Tables for, you guessed it, tabular data - * ---------------------------------------- */ -table { - width: 100%; - margin-bottom: 18px; - padding: 0; - font-size: 13px; - border-collapse: collapse; -} -table th, table td { - padding: 10px 10px 9px; - line-height: 18px; - text-align: left; -} -table th { - padding-top: 9px; - font-weight: bold; - vertical-align: middle; -} -table td { - vertical-align: top; - border-top: 1px solid #ddd; -} -table tbody th { - border-top: 1px solid #ddd; - vertical-align: top; -} -.condensed-table th, .condensed-table td { - padding: 5px 5px 4px; -} -.bordered-table { - border: 1px solid #ddd; - border-collapse: separate; - *border-collapse: collapse; - /* IE7, collapse table to remove spacing */ - - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -.bordered-table th + th, .bordered-table td + td, .bordered-table th + td { - border-left: 1px solid #ddd; -} -.bordered-table thead tr:first-child th:first-child, .bordered-table tbody tr:first-child td:first-child { - -webkit-border-radius: 4px 0 0 0; - -moz-border-radius: 4px 0 0 0; - border-radius: 4px 0 0 0; -} -.bordered-table thead tr:first-child th:last-child, .bordered-table tbody tr:first-child td:last-child { - -webkit-border-radius: 0 4px 0 0; - -moz-border-radius: 0 4px 0 0; - border-radius: 0 4px 0 0; -} -.bordered-table tbody tr:last-child td:first-child { - -webkit-border-radius: 0 0 0 4px; - -moz-border-radius: 0 0 0 4px; - border-radius: 0 0 0 4px; -} -.bordered-table tbody tr:last-child td:last-child { - -webkit-border-radius: 0 0 4px 0; - -moz-border-radius: 0 0 4px 0; - border-radius: 0 0 4px 0; -} -table .span1 { - width: 20px; -} -table .span2 { - width: 60px; -} -table .span3 { - width: 100px; -} -table .span4 { - width: 140px; -} -table .span5 { - width: 180px; -} -table .span6 { - width: 220px; -} -table .span7 { - width: 260px; -} -table .span8 { - width: 300px; -} -table .span9 { - width: 340px; -} -table .span10 { - width: 380px; -} -table .span11 { - width: 420px; -} -table .span12 { - width: 460px; -} -table .span13 { - width: 500px; -} -table .span14 { - width: 540px; -} -table .span15 { - width: 580px; -} -table .span16 { - width: 620px; -} -.zebra-striped tbody tr:nth-child(odd) td, .zebra-striped tbody tr:nth-child(odd) th { - background-color: #f9f9f9; -} -.zebra-striped tbody tr:hover td, .zebra-striped tbody tr:hover th { - background-color: #f5f5f5; -} -table .header { - cursor: pointer; -} -table .header:after { - content: ""; - float: right; - margin-top: 7px; - border-width: 0 4px 4px; - border-style: solid; - border-color: #000 transparent; - visibility: hidden; -} -table .headerSortUp, table .headerSortDown { - background-color: rgba(141, 192, 219, 0.25); - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); -} -table .header:hover:after { - visibility: visible; -} -table .headerSortDown:after, table .headerSortDown:hover:after { - visibility: visible; - filter: alpha(opacity=60); - -khtml-opacity: 0.6; - -moz-opacity: 0.6; - opacity: 0.6; -} -table .headerSortUp:after { - border-bottom: none; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid #000; - visibility: visible; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - filter: alpha(opacity=60); - -khtml-opacity: 0.6; - -moz-opacity: 0.6; - opacity: 0.6; -} -table .blue { - color: #049cdb; - border-bottom-color: #049cdb; -} -table .headerSortUp.blue, table .headerSortDown.blue { - background-color: #ade6fe; -} -table .green { - color: #46a546; - border-bottom-color: #46a546; -} -table .headerSortUp.green, table .headerSortDown.green { - background-color: #cdeacd; -} -table .red { - color: #9d261d; - border-bottom-color: #9d261d; -} -table .headerSortUp.red, table .headerSortDown.red { - background-color: #f4c8c5; -} -table .yellow { - color: #ffc40d; - border-bottom-color: #ffc40d; -} -table .headerSortUp.yellow, table .headerSortDown.yellow { - background-color: #fff6d9; -} -table .orange { - color: #f89406; - border-bottom-color: #f89406; -} -table .headerSortUp.orange, table .headerSortDown.orange { - background-color: #fee9cc; -} -table .purple { - color: #7a43b6; - border-bottom-color: #7a43b6; -} -table .headerSortUp.purple, table .headerSortDown.purple { - background-color: #e2d5f0; -} -/* Patterns.less - * Repeatable UI elements outside the base styles provided from the scaffolding - * ---------------------------------------------------------------------------- */ -.topbar { - height: 40px; - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 10000; - overflow: visible; -} -.topbar a { - color: #bfbfbf; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} -.topbar h3 a:hover, .topbar .brand:hover, .topbar ul .active > a { - background-color: #333; - background-color: rgba(255, 255, 255, 0.05); - color: #ffffff; - text-decoration: none; -} -.topbar h3 { - position: relative; -} -.topbar h3 a, .topbar .brand { - float: left; - display: block; - padding: 8px 20px 12px; - margin-left: -20px; - color: #ffffff; - font-size: 20px; - font-weight: 200; - line-height: 1; -} -.topbar p { - margin: 0; - line-height: 40px; -} -.topbar p a:hover { - background-color: transparent; - color: #ffffff; -} -.topbar form { - float: left; - margin: 5px 0 0 0; - position: relative; - filter: alpha(opacity=100); - -khtml-opacity: 1; - -moz-opacity: 1; - opacity: 1; -} -.topbar form.pull-right { - float: right; -} -.topbar input { - background-color: #444; - background-color: rgba(255, 255, 255, 0.3); - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: normal; - font-weight: 13px; - line-height: 1; - padding: 4px 9px; - color: #ffffff; - color: rgba(255, 255, 255, 0.75); - border: 1px solid #111; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.25); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.25); - -webkit-transition: none; - -moz-transition: none; - -ms-transition: none; - -o-transition: none; - transition: none; -} -.topbar input:-moz-placeholder { - color: #e6e6e6; -} -.topbar input::-webkit-input-placeholder { - color: #e6e6e6; -} -.topbar input:hover { - background-color: #bfbfbf; - background-color: rgba(255, 255, 255, 0.5); - color: #ffffff; -} -.topbar input:focus, .topbar input.focused { - outline: 0; - background-color: #ffffff; - color: #404040; - text-shadow: 0 1px 0 #ffffff; - border: 0; - padding: 5px 10px; - -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); - -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); - box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); -} -.topbar-inner, .topbar .fill { - background-color: #222; - background-color: #222222; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222)); - background-image: -moz-linear-gradient(top, #333333, #222222); - background-image: -ms-linear-gradient(top, #333333, #222222); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222)); - background-image: -webkit-linear-gradient(top, #333333, #222222); - background-image: -o-linear-gradient(top, #333333, #222222); - background-image: linear-gradient(top, #333333, #222222); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); -} -.topbar div > ul, .nav { - display: block; - float: left; - margin: 0 10px 0 0; - position: relative; - left: 0; -} -.topbar div > ul > li, .nav > li { - display: block; - float: left; -} -.topbar div > ul a, .nav a { - display: block; - float: none; - padding: 10px 10px 11px; - line-height: 19px; - text-decoration: none; -} -.topbar div > ul a:hover, .nav a:hover { - color: #ffffff; - text-decoration: none; -} -.topbar div > ul .active > a, .nav .active > a { - background-color: #222; - background-color: rgba(0, 0, 0, 0.5); -} -.topbar div > ul.secondary-nav, .nav.secondary-nav { - float: right; - margin-left: 10px; - margin-right: 0; -} -.topbar div > ul.secondary-nav .menu-dropdown, -.nav.secondary-nav .menu-dropdown, -.topbar div > ul.secondary-nav .dropdown-menu, -.nav.secondary-nav .dropdown-menu { - right: 0; - border: 0; -} -.topbar div > ul a.menu:hover, -.nav a.menu:hover, -.topbar div > ul li.open .menu, -.nav li.open .menu, -.topbar div > ul .dropdown-toggle:hover, -.nav .dropdown-toggle:hover, -.topbar div > ul .dropdown.open .dropdown-toggle, -.nav .dropdown.open .dropdown-toggle { - background: #444; - background: rgba(255, 255, 255, 0.05); -} -.topbar div > ul .menu-dropdown, -.nav .menu-dropdown, -.topbar div > ul .dropdown-menu, -.nav .dropdown-menu { - background-color: #333; -} -.topbar div > ul .menu-dropdown a.menu, -.nav .menu-dropdown a.menu, -.topbar div > ul .dropdown-menu a.menu, -.nav .dropdown-menu a.menu, -.topbar div > ul .menu-dropdown .dropdown-toggle, -.nav .menu-dropdown .dropdown-toggle, -.topbar div > ul .dropdown-menu .dropdown-toggle, -.nav .dropdown-menu .dropdown-toggle { - color: #ffffff; -} -.topbar div > ul .menu-dropdown a.menu.open, -.nav .menu-dropdown a.menu.open, -.topbar div > ul .dropdown-menu a.menu.open, -.nav .dropdown-menu a.menu.open, -.topbar div > ul .menu-dropdown .dropdown-toggle.open, -.nav .menu-dropdown .dropdown-toggle.open, -.topbar div > ul .dropdown-menu .dropdown-toggle.open, -.nav .dropdown-menu .dropdown-toggle.open { - background: #444; - background: rgba(255, 255, 255, 0.05); -} -.topbar div > ul .menu-dropdown li a, -.nav .menu-dropdown li a, -.topbar div > ul .dropdown-menu li a, -.nav .dropdown-menu li a { - color: #999; - text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5); -} -.topbar div > ul .menu-dropdown li a:hover, -.nav .menu-dropdown li a:hover, -.topbar div > ul .dropdown-menu li a:hover, -.nav .dropdown-menu li a:hover { - background-color: #191919; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919)); - background-image: -moz-linear-gradient(top, #292929, #191919); - background-image: -ms-linear-gradient(top, #292929, #191919); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919)); - background-image: -webkit-linear-gradient(top, #292929, #191919); - background-image: -o-linear-gradient(top, #292929, #191919); - background-image: linear-gradient(top, #292929, #191919); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0); - color: #ffffff; -} -.topbar div > ul .menu-dropdown .active a, -.nav .menu-dropdown .active a, -.topbar div > ul .dropdown-menu .active a, -.nav .dropdown-menu .active a { - color: #ffffff; -} -.topbar div > ul .menu-dropdown .divider, -.nav .menu-dropdown .divider, -.topbar div > ul .dropdown-menu .divider, -.nav .dropdown-menu .divider { - background-color: #222; - border-color: #444; -} -.topbar ul .menu-dropdown li a, .topbar ul .dropdown-menu li a { - padding: 4px 15px; -} -li.menu, .dropdown { - position: relative; -} -a.menu:after, .dropdown-toggle:after { - width: 0; - height: 0; - display: inline-block; - content: "↓"; - text-indent: -99999px; - vertical-align: top; - margin-top: 8px; - margin-left: 4px; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid #ffffff; - filter: alpha(opacity=50); - -khtml-opacity: 0.5; - -moz-opacity: 0.5; - opacity: 0.5; -} -.menu-dropdown, .dropdown-menu { - background-color: #ffffff; - float: left; - display: none; - position: absolute; - top: 40px; - z-index: 900; - min-width: 160px; - max-width: 220px; - _width: 160px; - margin-left: 0; - margin-right: 0; - padding: 6px 0; - zoom: 1; - border-color: #999; - border-color: rgba(0, 0, 0, 0.2); - border-style: solid; - border-width: 0 1px 1px; - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; - -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; -} -.menu-dropdown li, .dropdown-menu li { - float: none; - display: block; - background-color: none; -} -.menu-dropdown .divider, .dropdown-menu .divider { - height: 1px; - margin: 5px 0; - overflow: hidden; - background-color: #eee; - border-bottom: 1px solid #ffffff; -} -.topbar .dropdown-menu a, .dropdown-menu a { - display: block; - padding: 4px 15px; - clear: both; - font-weight: normal; - line-height: 18px; - color: #808080; - text-shadow: 0 1px 0 #ffffff; -} -.topbar .dropdown-menu a:hover, -.dropdown-menu a:hover, -.topbar .dropdown-menu a.hover, -.dropdown-menu a.hover { - background-color: #dddddd; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd)); - background-image: -moz-linear-gradient(top, #eeeeee, #dddddd); - background-image: -ms-linear-gradient(top, #eeeeee, #dddddd); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd)); - background-image: -webkit-linear-gradient(top, #eeeeee, #dddddd); - background-image: -o-linear-gradient(top, #eeeeee, #dddddd); - background-image: linear-gradient(top, #eeeeee, #dddddd); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0); - color: #404040; - text-decoration: none; - -webkit-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.025), inset 0 -1px rgba(0, 0, 0, 0.025); - -moz-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.025), inset 0 -1px rgba(0, 0, 0, 0.025); - box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.025), inset 0 -1px rgba(0, 0, 0, 0.025); -} -.open .menu, -.dropdown.open .menu, -.open .dropdown-toggle, -.dropdown.open .dropdown-toggle { - color: #ffffff; - background: #ccc; - background: rgba(0, 0, 0, 0.3); -} -.open .menu-dropdown, -.dropdown.open .menu-dropdown, -.open .dropdown-menu, -.dropdown.open .dropdown-menu { - display: block; -} -.tabs, .pills { - margin: 0 0 18px; - padding: 0; - list-style: none; - zoom: 1; -} -.tabs:before, -.pills:before, -.tabs:after, -.pills:after { - display: table; - content: ""; - zoom: 1; -} -.tabs:after, .pills:after { - clear: both; -} -.tabs > li, .pills > li { - float: left; -} -.tabs > li > a, .pills > li > a { - display: block; -} -.tabs { - border-color: #ddd; - border-style: solid; - border-width: 0 0 1px; -} -.tabs > li { - position: relative; - margin-bottom: -1px; -} -.tabs > li > a { - padding: 0 15px; - margin-right: 2px; - line-height: 34px; - border: 1px solid transparent; - -webkit-border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; -} -.tabs > li > a:hover { - text-decoration: none; - background-color: #eee; - border-color: #eee #eee #ddd; -} -.tabs .active > a, .tabs .active > a:hover { - color: #808080; - background-color: #ffffff; - border: 1px solid #ddd; - border-bottom-color: transparent; - cursor: default; -} -.tabs .menu-dropdown, .tabs .dropdown-menu { - top: 35px; - border-width: 1px; - -webkit-border-radius: 0 6px 6px 6px; - -moz-border-radius: 0 6px 6px 6px; - border-radius: 0 6px 6px 6px; -} -.tabs a.menu:after, .tabs .dropdown-toggle:after { - border-top-color: #999; - margin-top: 15px; - margin-left: 5px; -} -.tabs li.open.menu .menu, .tabs .open.dropdown .dropdown-toggle { - border-color: #999; -} -.tabs li.open a.menu:after, .tabs .dropdown.open .dropdown-toggle:after { - border-top-color: #555; -} -.pills a { - margin: 5px 3px 5px 0; - padding: 0 15px; - line-height: 30px; - text-shadow: 0 1px 1px #ffffff; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} -.pills a:hover { - color: #ffffff; - text-decoration: none; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); - background-color: #00438a; -} -.pills .active a { - color: #ffffff; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); - background-color: #0069d6; -} -.pills-vertical > li { - float: none; -} -.tab-content > .tab-pane, -.pill-content > .pill-pane, -.tab-content > div, -.pill-content > div { - display: none; -} -.tab-content > .active, .pill-content > .active { - display: block; -} -.breadcrumb { - padding: 7px 14px; - margin: 0 0 18px; - background-color: #f5f5f5; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5)); - background-image: -moz-linear-gradient(top, #ffffff, #f5f5f5); - background-image: -ms-linear-gradient(top, #ffffff, #f5f5f5); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5)); - background-image: -webkit-linear-gradient(top, #ffffff, #f5f5f5); - background-image: -o-linear-gradient(top, #ffffff, #f5f5f5); - background-image: linear-gradient(top, #ffffff, #f5f5f5); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0); - border: 1px solid #ddd; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - -webkit-box-shadow: inset 0 1px 0 #ffffff; - -moz-box-shadow: inset 0 1px 0 #ffffff; - box-shadow: inset 0 1px 0 #ffffff; -} -.breadcrumb li { - display: inline; - text-shadow: 0 1px 0 #ffffff; -} -.breadcrumb .divider { - padding: 0 5px; - color: #bfbfbf; -} -.breadcrumb .active a { - color: #404040; -} -.hero-unit { - background-color: #f5f5f5; - margin-bottom: 30px; - padding: 60px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} -.hero-unit h1 { - margin-bottom: 0; - font-size: 60px; - line-height: 1; - letter-spacing: -1px; -} -.hero-unit p { - font-size: 18px; - font-weight: 200; - line-height: 27px; -} -footer { - margin-top: 17px; - padding-top: 17px; - border-top: 1px solid #eee; -} -.page-header { - margin-bottom: 17px; - border-bottom: 1px solid #ddd; - -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); -} -.page-header h1 { - margin-bottom: 8px; -} -.btn.danger, -.alert-message.danger, -.btn.danger:hover, -.alert-message.danger:hover, -.btn.error, -.alert-message.error, -.btn.error:hover, -.alert-message.error:hover, -.btn.success, -.alert-message.success, -.btn.success:hover, -.alert-message.success:hover, -.btn.info, -.alert-message.info, -.btn.info:hover, -.alert-message.info:hover { - color: #ffffff; -} -.btn .close, .alert-message .close { - font-family: Arial, sans-serif; - line-height: 18px; -} -.btn.danger, -.alert-message.danger, -.btn.error, -.alert-message.error { - background-color: #c43c35; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35)); - background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35)); - background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -o-linear-gradient(top, #ee5f5b, #c43c35); - background-image: linear-gradient(top, #ee5f5b, #c43c35); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0); - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - border-color: #c43c35 #c43c35 #882a25; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -} -.btn.success, .alert-message.success { - background-color: #57a957; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957)); - background-image: -moz-linear-gradient(top, #62c462, #57a957); - background-image: -ms-linear-gradient(top, #62c462, #57a957); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957)); - background-image: -webkit-linear-gradient(top, #62c462, #57a957); - background-image: -o-linear-gradient(top, #62c462, #57a957); - background-image: linear-gradient(top, #62c462, #57a957); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0); - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - border-color: #57a957 #57a957 #3d773d; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -} -.btn.info, .alert-message.info { - background-color: #339bb9; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9)); - background-image: -moz-linear-gradient(top, #5bc0de, #339bb9); - background-image: -ms-linear-gradient(top, #5bc0de, #339bb9); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9)); - background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9); - background-image: -o-linear-gradient(top, #5bc0de, #339bb9); - background-image: linear-gradient(top, #5bc0de, #339bb9); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0); - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - border-color: #339bb9 #339bb9 #22697d; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -} -.btn { - cursor: pointer; - display: inline-block; - background-color: #e6e6e6; - background-repeat: no-repeat; - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); - background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); - padding: 5px 14px 6px; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - color: #333; - font-size: 13px; - line-height: normal; - border: 1px solid #ccc; - border-bottom-color: #bbb; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -webkit-transition: 0.1s linear all; - -moz-transition: 0.1s linear all; - -ms-transition: 0.1s linear all; - -o-transition: 0.1s linear all; - transition: 0.1s linear all; -} -.btn:hover { - background-position: 0 -15px; - color: #333; - text-decoration: none; -} -.btn:focus { - outline: 1px dotted #666; -} -.btn.primary { - color: #ffffff; - background-color: #0064cd; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd)); - background-image: -moz-linear-gradient(top, #049cdb, #0064cd); - background-image: -ms-linear-gradient(top, #049cdb, #0064cd); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd)); - background-image: -webkit-linear-gradient(top, #049cdb, #0064cd); - background-image: -o-linear-gradient(top, #049cdb, #0064cd); - background-image: linear-gradient(top, #049cdb, #0064cd); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0); - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - border-color: #0064cd #0064cd #003f81; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -} -.btn.active, .btn:active { - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); -} -.btn.disabled { - cursor: default; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - filter: alpha(opacity=65); - -khtml-opacity: 0.65; - -moz-opacity: 0.65; - opacity: 0.65; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} -.btn[disabled] { - cursor: default; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - filter: alpha(opacity=65); - -khtml-opacity: 0.65; - -moz-opacity: 0.65; - opacity: 0.65; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} -.btn.large { - font-size: 15px; - line-height: normal; - padding: 9px 14px 9px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} -.btn.small { - padding: 7px 9px 7px; - font-size: 11px; -} -:root .alert-message, :root .btn { - border-radius: 0 \0; -} -button.btn::-moz-focus-inner, input[type=submit].btn::-moz-focus-inner { - padding: 0; - border: 0; -} -.close { - float: right; - color: #000000; - font-size: 20px; - font-weight: bold; - line-height: 13.5px; - text-shadow: 0 1px 0 #ffffff; - filter: alpha(opacity=25); - -khtml-opacity: 0.25; - -moz-opacity: 0.25; - opacity: 0.25; -} -.close:hover { - color: #000000; - text-decoration: none; - filter: alpha(opacity=40); - -khtml-opacity: 0.4; - -moz-opacity: 0.4; - opacity: 0.4; -} -.alert-message { - position: relative; - padding: 7px 15px; - margin-bottom: 18px; - color: #404040; - background-color: #eedc94; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94)); - background-image: -moz-linear-gradient(top, #fceec1, #eedc94); - background-image: -ms-linear-gradient(top, #fceec1, #eedc94); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94)); - background-image: -webkit-linear-gradient(top, #fceec1, #eedc94); - background-image: -o-linear-gradient(top, #fceec1, #eedc94); - background-image: linear-gradient(top, #fceec1, #eedc94); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0); - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - border-color: #eedc94 #eedc94 #e4c652; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - border-width: 1px; - border-style: solid; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); -} -.alert-message .close { - margin-top: 1px; - *margin-top: 0; -} -.alert-message a { - font-weight: bold; - color: #404040; -} -.alert-message.danger p a, -.alert-message.error p a, -.alert-message.success p a, -.alert-message.info p a { - color: #ffffff; -} -.alert-message h5 { - line-height: 18px; -} -.alert-message p { - margin-bottom: 0; -} -.alert-message div { - margin-top: 5px; - margin-bottom: 2px; - line-height: 28px; -} -.alert-message .btn { - -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); -} -.alert-message.block-message { - background-image: none; - background-color: #fdf5d9; - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - padding: 14px; - border-color: #fceec1; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} -.alert-message.block-message ul, .alert-message.block-message p { - margin-right: 30px; -} -.alert-message.block-message ul { - margin-bottom: 0; -} -.alert-message.block-message li { - color: #404040; -} -.alert-message.block-message .alert-actions { - margin-top: 5px; -} -.alert-message.block-message.error, .alert-message.block-message.success, .alert-message.block-message.info { - color: #404040; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); -} -.alert-message.block-message.error { - background-color: #fddfde; - border-color: #fbc7c6; -} -.alert-message.block-message.success { - background-color: #d1eed1; - border-color: #bfe7bf; -} -.alert-message.block-message.info { - background-color: #ddf4fb; - border-color: #c6edf9; -} -.alert-message.block-message.danger p a, -.alert-message.block-message.error p a, -.alert-message.block-message.success p a, -.alert-message.block-message.info p a { - color: #404040; -} -.pagination { - height: 36px; - margin: 18px 0; -} -.pagination ul { - float: left; - margin: 0; - border: 1px solid #ddd; - border: 1px solid rgba(0, 0, 0, 0.15); - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} -.pagination li { - display: inline; -} -.pagination a { - float: left; - padding: 0 14px; - line-height: 34px; - border-right: 1px solid; - border-right-color: #ddd; - border-right-color: rgba(0, 0, 0, 0.15); - *border-right-color: #ddd; - /* IE6-7 */ - - text-decoration: none; -} -.pagination a:hover, .pagination .active a { - background-color: #c7eefe; -} -.pagination .disabled a, .pagination .disabled a:hover { - background-color: transparent; - color: #bfbfbf; -} -.pagination .next a { - border: 0; -} -.well { - background-color: #f5f5f5; - margin-bottom: 20px; - padding: 19px; - min-height: 20px; - border: 1px solid #eee; - border: 1px solid rgba(0, 0, 0, 0.05); - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -} -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, 0.15); -} -.modal-backdrop { - background-color: #000000; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 10000; -} -.modal-backdrop.fade { - opacity: 0; -} -.modal-backdrop, .modal-backdrop.fade.in { - filter: alpha(opacity=80); - -khtml-opacity: 0.8; - -moz-opacity: 0.8; - opacity: 0.8; -} -.modal { - position: fixed; - top: 50%; - left: 50%; - z-index: 11000; - width: 560px; - margin: -250px 0 0 -280px; - background-color: #ffffff; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, 0.3); - *border: 1px solid #999; - /* IE6-7 */ - - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; -} -.modal .close { - margin-top: 7px; -} -.modal.fade { - -webkit-transition: opacity .3s linear, top .3s ease-out; - -moz-transition: opacity .3s linear, top .3s ease-out; - -ms-transition: opacity .3s linear, top .3s ease-out; - -o-transition: opacity .3s linear, top .3s ease-out; - transition: opacity .3s linear, top .3s ease-out; - top: -25%; -} -.modal.fade.in { - top: 50%; -} -.modal-header { - border-bottom: 1px solid #eee; - padding: 5px 15px; -} -.modal-body { - padding: 15px; -} -.modal-body form { - margin-bottom: 0; -} -.modal-footer { - background-color: #f5f5f5; - padding: 14px 15px 15px; - border-top: 1px solid #ddd; - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; - -webkit-box-shadow: inset 0 1px 0 #ffffff; - -moz-box-shadow: inset 0 1px 0 #ffffff; - box-shadow: inset 0 1px 0 #ffffff; - zoom: 1; - margin-bottom: 0; -} -.modal-footer:before, .modal-footer:after { - display: table; - content: ""; - zoom: 1; -} -.modal-footer:after { - clear: both; -} -.modal-footer .btn { - float: right; - margin-left: 5px; -} -.modal .popover, .modal .twipsy { - z-index: 12000; -} -.twipsy { - display: block; - position: absolute; - visibility: visible; - padding: 5px; - font-size: 11px; - z-index: 1000; - filter: alpha(opacity=80); - -khtml-opacity: 0.8; - -moz-opacity: 0.8; - opacity: 0.8; -} -.twipsy.fade.in { - filter: alpha(opacity=80); - -khtml-opacity: 0.8; - -moz-opacity: 0.8; - opacity: 0.8; -} -.twipsy.above .twipsy-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 5px solid #000000; -} -.twipsy.left .twipsy-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-left: 5px solid #000000; -} -.twipsy.below .twipsy-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-bottom: 5px solid #000000; -} -.twipsy.right .twipsy-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-right: 5px solid #000000; -} -.twipsy-inner { - padding: 3px 8px; - background-color: #000000; - color: white; - text-align: center; - max-width: 200px; - text-decoration: none; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -.twipsy-arrow { - position: absolute; - width: 0; - height: 0; -} -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1000; - padding: 5px; - display: none; -} -.popover.above .arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 5px solid #000000; -} -.popover.right .arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-right: 5px solid #000000; -} -.popover.below .arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-bottom: 5px solid #000000; -} -.popover.left .arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-left: 5px solid #000000; -} -.popover .arrow { - position: absolute; - width: 0; - height: 0; -} -.popover .inner { - background: #000000; - background: rgba(0, 0, 0, 0.8); - padding: 3px; - overflow: hidden; - width: 280px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); -} -.popover .title { - background-color: #f5f5f5; - padding: 9px 15px; - line-height: 1; - -webkit-border-radius: 3px 3px 0 0; - -moz-border-radius: 3px 3px 0 0; - border-radius: 3px 3px 0 0; - border-bottom: 1px solid #eee; -} -.popover .content { - background-color: #ffffff; - padding: 14px; - -webkit-border-radius: 0 0 3px 3px; - -moz-border-radius: 0 0 3px 3px; - border-radius: 0 0 3px 3px; - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; -} -.popover .content p, .popover .content ul, .popover .content ol { - margin-bottom: 0; -} -.fade { - -webkit-transition: opacity 0.15s linear; - -moz-transition: opacity 0.15s linear; - -ms-transition: opacity 0.15s linear; - -o-transition: opacity 0.15s linear; - transition: opacity 0.15s linear; - opacity: 0; -} -.fade.in { - opacity: 1; -} -.label { - padding: 1px 3px 2px; - font-size: 9.75px; - font-weight: bold; - color: #ffffff; - text-transform: uppercase; - white-space: nowrap; - background-color: #bfbfbf; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} -.label.important { - background-color: #c43c35; -} -.label.warning { - background-color: #f89406; -} -.label.success { - background-color: #46a546; -} -.label.notice { - background-color: #62cffc; -} -.media-grid { - margin-left: -20px; - margin-bottom: 0; - zoom: 1; -} -.media-grid:before, .media-grid:after { - display: table; - content: ""; - zoom: 1; -} -.media-grid:after { - clear: both; -} -.media-grid li { - display: inline; -} -.media-grid a { - float: left; - padding: 4px; - margin: 0 0 18px 20px; - border: 1px solid #ddd; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); -} -.media-grid a img { - display: block; -} -.media-grid a:hover { - border-color: #0069d6; - -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); -} diff --git a/docsite/_themes/bootstrap/theme.conf b/docsite/_themes/bootstrap/theme.conf deleted file mode 100644 index 64f9efd569..0000000000 --- a/docsite/_themes/bootstrap/theme.conf +++ /dev/null @@ -1,5 +0,0 @@ -# Twitter Bootstrap Theme -[theme] -inherit = basic -stylesheet = basic.css -pygments_style = tango diff --git a/docsite/_themes/srtd/__init__.py b/docsite/_themes/srtd/__init__.py new file mode 100644 index 0000000000..1440863d68 --- /dev/null +++ b/docsite/_themes/srtd/__init__.py @@ -0,0 +1,17 @@ +"""Sphinx ReadTheDocs theme. + +From https://github.com/ryan-roemer/sphinx-bootstrap-theme. + +""" +import os + +VERSION = (0, 1, 5) + +__version__ = ".".join(str(v) for v in VERSION) +__version_full__ = __version__ + + +def get_html_theme_path(): + """Return list of HTML theme paths.""" + cur_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + return cur_dir diff --git a/docsite/_themes/srtd/breadcrumbs.html b/docsite/_themes/srtd/breadcrumbs.html new file mode 100644 index 0000000000..834cc649b2 --- /dev/null +++ b/docsite/_themes/srtd/breadcrumbs.html @@ -0,0 +1,11 @@ +
      +
    • Docs »
    • +
    • {{ title }}
    • + {% if not pagename.endswith('_module') and (not 'list_of' in pagename) and (not 'category' in pagename) %} +
    • + Edit on GitHub +
    • + {% endif %} +
    +
    + diff --git a/docsite/_themes/srtd/footer.html b/docsite/_themes/srtd/footer.html new file mode 100644 index 0000000000..ea0efc35ad --- /dev/null +++ b/docsite/_themes/srtd/footer.html @@ -0,0 +1,25 @@ +
    + {% if next or prev %} + + {% endif %} + +
    + +

    + © Copyright 2013 AnsibleWorks. + + {%- if last_updated %} + {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} + {%- endif %} +

    + +Ansible docs are generated from GitHub sources using Sphinx using a theme provided by Read the Docs. {% if pagename.endswith("_module") %}. Module documentation is not edited directly, but is generated from the source code for the modules. To submit an update to module docs, edit the 'DOCUMENTATION' metadata in the module source tree. {% endif %} + +
    diff --git a/docsite/_themes/srtd/layout.html b/docsite/_themes/srtd/layout.html new file mode 100644 index 0000000000..3cfa034706 --- /dev/null +++ b/docsite/_themes/srtd/layout.html @@ -0,0 +1,187 @@ +{# TEMPLATE VAR SETTINGS #} +{%- set url_root = pathto('', 1) %} +{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} +{%- if not embedded and docstitle %} + {%- set titlesuffix = " — "|safe + docstitle|e %} +{%- else %} + {%- set titlesuffix = "" %} +{%- endif %} + + + + + + + + + + + + + {% block htmltitle %} + {{ title|striptags|e }}{{ titlesuffix }} + {% endblock %} + + {# FAVICON #} + {% if favicon %} + + {% endif %} + + {# CSS #} + + + + {# JS #} + {% if not embedded %} + + + {%- for scriptfile in script_files %} + + {%- endfor %} + + {% if use_opensearch %} + + {% endif %} + + {% endif %} + + {# RTD hosts these file themselves, so just load on non RTD builds #} + {% if not READTHEDOCS %} + + + {% endif %} + + {% for cssfile in css_files %} + + {% endfor %} + + {%- block linktags %} + {%- if hasdoc('about') %} + + {%- endif %} + {%- if hasdoc('genindex') %} + + {%- endif %} + {%- if hasdoc('search') %} + + {%- endif %} + {%- if hasdoc('copyright') %} + + {%- endif %} + + {%- if parents %} + + {%- endif %} + {%- if next %} + + {%- endif %} + {%- if prev %} + + {%- endif %} + {%- endblock %} + {%- block extrahead %} {% endblock %} + + + + + + + + + +
    + + {# SIDE NAV, TOGGLES ON MOBILE #} + + +
    + + {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #} + + + + {# PAGE CONTENT #} +
    +
    + {% include "breadcrumbs.html" %} +
    + {% block body %}{% endblock %} +
    +
    + {% include "footer.html" %} +
    +
    + +
    + +
    + {% include "versions.html" %} + + + + + + + diff --git a/docsite/_themes/srtd/layout_old.html b/docsite/_themes/srtd/layout_old.html new file mode 100644 index 0000000000..deb8df2a1a --- /dev/null +++ b/docsite/_themes/srtd/layout_old.html @@ -0,0 +1,205 @@ +{# + basic/layout.html + ~~~~~~~~~~~~~~~~~ + + Master layout template for Sphinx themes. + + :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- block doctype -%} + +{%- endblock %} +{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} +{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} +{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and + (sidebars != []) %} +{%- set url_root = pathto('', 1) %} +{# XXX necessary? #} +{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} +{%- if not embedded and docstitle %} + {%- set titlesuffix = " — "|safe + docstitle|e %} +{%- else %} + {%- set titlesuffix = "" %} +{%- endif %} + +{%- macro relbar() %} + +{%- endmacro %} + +{%- macro sidebar() %} + {%- if render_sidebar %} +
    +
    + {%- block sidebarlogo %} + {%- if logo %} + + {%- endif %} + {%- endblock %} + {%- if sidebars != None %} + {#- new style sidebar: explicitly include/exclude templates #} + {%- for sidebartemplate in sidebars %} + {%- include sidebartemplate %} + {%- endfor %} + {%- else %} + {#- old style sidebars: using blocks -- should be deprecated #} + {%- block sidebartoc %} + {%- include "localtoc.html" %} + {%- endblock %} + {%- block sidebarrel %} + {%- include "relations.html" %} + {%- endblock %} + {%- block sidebarsourcelink %} + {%- include "sourcelink.html" %} + {%- endblock %} + {%- if customsidebar %} + {%- include customsidebar %} + {%- endif %} + {%- block sidebarsearch %} + {%- include "searchbox.html" %} + {%- endblock %} + {%- endif %} +
    +
    + {%- endif %} +{%- endmacro %} + +{%- macro script() %} + + {%- for scriptfile in script_files %} + + {%- endfor %} +{%- endmacro %} + +{%- macro css() %} + + + {%- for cssfile in css_files %} + + {%- endfor %} +{%- endmacro %} + + + + + {{ metatags }} + {%- block htmltitle %} + {{ title|striptags|e }}{{ titlesuffix }} + {%- endblock %} + {{ css() }} + {%- if not embedded %} + {{ script() }} + {%- if use_opensearch %} + + {%- endif %} + {%- if favicon %} + + {%- endif %} + {%- endif %} +{%- block linktags %} + {%- if hasdoc('about') %} + + {%- endif %} + {%- if hasdoc('genindex') %} + + {%- endif %} + {%- if hasdoc('search') %} + + {%- endif %} + {%- if hasdoc('copyright') %} + + {%- endif %} + + {%- if parents %} + + {%- endif %} + {%- if next %} + + {%- endif %} + {%- if prev %} + + {%- endif %} +{%- endblock %} +{%- block extrahead %} {% endblock %} + + +{%- block header %}{% endblock %} + +{%- block relbar1 %}{{ relbar() }}{% endblock %} + +{%- block content %} + {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %} + +
    + {%- block document %} +
    + {%- if render_sidebar %} +
    + {%- endif %} +
    + {% block body %} {% endblock %} +
    + {%- if render_sidebar %} +
    + {%- endif %} +
    + {%- endblock %} + + {%- block sidebar2 %}{{ sidebar() }}{% endblock %} +
    +
    +{%- endblock %} + +{%- block relbar2 %}{{ relbar() }}{% endblock %} + +{%- block footer %} + +

    asdf asdf asdf asdf 22

    +{%- endblock %} + + + diff --git a/docsite/_themes/srtd/search.html b/docsite/_themes/srtd/search.html new file mode 100644 index 0000000000..d8bbe69014 --- /dev/null +++ b/docsite/_themes/srtd/search.html @@ -0,0 +1,50 @@ +{# + basic/search.html + ~~~~~~~~~~~~~~~~~ + + Template for the search page. + + :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- extends "layout.html" %} +{% set title = _('Search') %} +{% set script_files = script_files + ['_static/searchtools.js'] %} +{% block extrahead %} + + {# this is used when loading the search index using $.ajax fails, + such as on Chrome for documents on localhost #} + + {{ super() }} +{% endblock %} +{% block body %} + + + {% if search_performed %} +

    {{ _('Search Results') }}

    + {% if not search_results %} +

    {{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}

    + {% endif %} + {% endif %} +
    + {% if search_results %} +
      + {% for href, caption, context in search_results %} +
    • + {{ caption }} +

      {{ context|e }}

      +
    • + {% endfor %} +
    + {% endif %} +
    +{% endblock %} diff --git a/docsite/_themes/srtd/searchbox.html b/docsite/_themes/srtd/searchbox.html new file mode 100644 index 0000000000..b79bcc3b23 --- /dev/null +++ b/docsite/_themes/srtd/searchbox.html @@ -0,0 +1,61 @@ + + + + +
    + + + +
    + + + + diff --git a/docsite/_themes/srtd/static/css/badge_only.css b/docsite/_themes/srtd/static/css/badge_only.css new file mode 100644 index 0000000000..7fccc414bb --- /dev/null +++ b/docsite/_themes/srtd/static/css/badge_only.css @@ -0,0 +1 @@ +.font-smooth,.icon:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:fontawesome-webfont;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#fontawesome-webfont") format("svg")}.icon:before{display:inline-block;font-family:fontawesome-webfont;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .icon{display:inline-block;text-decoration:inherit}li .icon{display:inline-block}li .icon-large:before,li .icon-large:before{width:1.875em}ul.icons{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.icons li .icon{width:0.8em}ul.icons li .icon-large:before,ul.icons li .icon-large:before{vertical-align:baseline}.icon-book:before{content:"\f02d"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}} diff --git a/docsite/_themes/srtd/static/css/theme.css b/docsite/_themes/srtd/static/css/theme.css new file mode 100644 index 0000000000..0d34ae03dd --- /dev/null +++ b/docsite/_themes/srtd/static/css/theme.css @@ -0,0 +1,4636 @@ +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { + display: block; +} + +audio, canvas, video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +audio:not([controls]) { + display: none; +} + +[hidden] { + display: none; +} + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + margin: 0; +} + +a:hover, a:active { + outline: 0; +} + +abbr[title] { + border-bottom: 1px dotted; +} + +b, strong { + font-weight: bold; +} + +blockquote { + margin: 0; +} + +dfn { + font-style: italic; +} + +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #ccc; + margin: 20px 0; + padding: 0; +} + +ins { + background: #ff9; + color: #000; + text-decoration: none; +} + +mark { + background: #ff0; + color: #000; + font-style: italic; + font-weight: bold; +} + +pre, code, .rst-content tt, kbd, samp { + font-family: monospace, serif; + _font-family: "courier new", monospace; + font-size: 1em; +} + +pre { + white-space: pre; +} + +q { + quotes: none; +} + +q:before, q:after { + content: ""; + content: none; +} + +small { + font-size: 85%; +} + +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +ul, ol, dl { + margin: 0; + padding: 0; + list-style: none; + list-style-image: none; +} + +li { + list-style: none; +} + +dd { + margin: 0; +} + +img { + border: 0; + -ms-interpolation-mode: bicubic; + vertical-align: middle; + max-width: 100%; +} + +svg:not(:root) { + overflow: hidden; +} + +figure { + margin: 0; +} + +form { + margin: 0; +} + +fieldset { + border: 0; + margin: 0; + padding: 0; +} + +label { + cursor: pointer; +} + +legend { + border: 0; + *margin-left: -7px; + padding: 0; + white-space: normal; +} + +button, input, select, textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; + *vertical-align: middle; +} + +button, input { + line-height: normal; +} + +button, input[type="button"], input[type="reset"], input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; + *overflow: visible; +} + +button[disabled], input[disabled] { + cursor: default; +} + +input[type="checkbox"], input[type="radio"] { + box-sizing: border-box; + padding: 0; + *width: 13px; + *height: 13px; +} + +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; +} + +button::-moz-focus-inner, input::-moz-focus-inner { + border: 0; + padding: 0; +} + +textarea { + overflow: auto; + vertical-align: top; + resize: vertical; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td { + vertical-align: top; +} + +.chromeframe { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} + +.ir { + display: block; + border: 0; + text-indent: -999em; + overflow: hidden; + background-color: transparent; + background-repeat: no-repeat; + text-align: left; + direction: ltr; + *line-height: 0; +} + +.ir br { + display: none; +} + +.hidden { + display: none !important; + visibility: hidden; +} + +.visuallyhidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { + clip: auto; + height: auto; + margin: 0; + overflow: visible; + position: static; + width: auto; +} + +.invisible { + visibility: hidden; +} + +.relative { + position: relative; +} + +big, small { + font-size: 100%; +} + +@media print { + html, body, section { + background: none !important; + } + + * { + box-shadow: none !important; + text-shadow: none !important; + filter: none !important; + -ms-filter: none !important; + } + + a, a:visited { + text-decoration: underline; + } + + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { + content: ""; + } + + pre, blockquote { + page-break-inside: avoid; + } + + thead { + display: table-header-group; + } + + tr, img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + @page { + margin: 0.5cm; + } + + p, h2, h3 { + orphans: 3; + widows: 3; + } + + h2, h3 { + page-break-after: avoid; + } +} + +.font-smooth, .icon:before, .wy-inline-validate.wy-inline-validate-success .wy-input-context:before, .wy-inline-validate.wy-inline-validate-danger .wy-input-context:before, .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .wy-input-context:before, .wy-tag-input-group .wy-tag .wy-tag-remove:before, .rst-content .admonition-title:before, .rst-content h1 .headerlink:before, .rst-content h2 .headerlink:before, .rst-content h3 .headerlink:before, .rst-content h4 .headerlink:before, .rst-content h5 .headerlink:before, .rst-content h6 .headerlink:before, .rst-content dl dt .headerlink:before, .wy-alert, .rst-content .note, .rst-content .attention, .rst-content .caution, .rst-content .danger, .rst-content .error, .rst-content .hint, .rst-content .important, .rst-content .tip, .rst-content .warning, .btn, input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"], select, textarea, .wy-tag-input-group, .wy-menu-vertical li.on a, .wy-menu-vertical li.current>a, .wy-side-nav-search>a, .wy-side-nav-search .wy-dropdown>a, .wy-nav-top a { + -webkit-font-smoothing: antialiased; +} + +.clearfix { + *zoom: 1; +} + +.clearfix:before, .clearfix:after { + display: table; + content: ""; +} + +.clearfix:after { + clear: both; +} + +@font-face { + font-family: fontawesome-webfont; + font-weight: normal; + font-style: normal; + src: url("../font/fontawesome_webfont.eot"); + src: url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"), url("../font/fontawesome_webfont.woff") format("woff"), url("../font/fontawesome_webfont.ttf") format("truetype"), url("../font/fontawesome_webfont.svg#fontawesome-webfont") format("svg"); +} + +.icon:before, .wy-inline-validate.wy-inline-validate-success .wy-input-context:before, .wy-inline-validate.wy-inline-validate-danger .wy-input-context:before, .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .wy-input-context:before, .wy-tag-input-group .wy-tag .wy-tag-remove:before, .rst-content .admonition-title:before, .rst-content h1 .headerlink:before, .rst-content h2 .headerlink:before, .rst-content h3 .headerlink:before, .rst-content h4 .headerlink:before, .rst-content h5 .headerlink:before, .rst-content h6 .headerlink:before, .rst-content dl dt .headerlink:before { + display: inline-block; + font-family: fontawesome-webfont; + font-style: normal; + font-weight: normal; + line-height: 1; + text-decoration: inherit; +} + +a .icon, a .wy-inline-validate.wy-inline-validate-success .wy-input-context, .wy-inline-validate.wy-inline-validate-success a .wy-input-context, a .wy-inline-validate.wy-inline-validate-danger .wy-input-context, .wy-inline-validate.wy-inline-validate-danger a .wy-input-context, a .wy-inline-validate.wy-inline-validate-warning .wy-input-context, .wy-inline-validate.wy-inline-validate-warning a .wy-input-context, a .wy-inline-validate.wy-inline-validate-info .wy-input-context, .wy-inline-validate.wy-inline-validate-info a .wy-input-context, a .wy-tag-input-group .wy-tag .wy-tag-remove, .wy-tag-input-group .wy-tag a .wy-tag-remove, a .rst-content .admonition-title, .rst-content a .admonition-title, a .rst-content h1 .headerlink, .rst-content h1 a .headerlink, a .rst-content h2 .headerlink, .rst-content h2 a .headerlink, a .rst-content h3 .headerlink, .rst-content h3 a .headerlink, a .rst-content h4 .headerlink, .rst-content h4 a .headerlink, a .rst-content h5 .headerlink, .rst-content h5 a .headerlink, a .rst-content h6 .headerlink, .rst-content h6 a .headerlink, a .rst-content dl dt .headerlink, .rst-content dl dt a .headerlink { + display: inline-block; + text-decoration: inherit; +} + +.icon-large:before { + vertical-align: -10%; + font-size: 1.33333em; +} + +.btn .icon, .btn .wy-inline-validate.wy-inline-validate-success .wy-input-context, .wy-inline-validate.wy-inline-validate-success .btn .wy-input-context, .btn .wy-inline-validate.wy-inline-validate-danger .wy-input-context, .wy-inline-validate.wy-inline-validate-danger .btn .wy-input-context, .btn .wy-inline-validate.wy-inline-validate-warning .wy-input-context, .wy-inline-validate.wy-inline-validate-warning .btn .wy-input-context, .btn .wy-inline-validate.wy-inline-validate-info .wy-input-context, .wy-inline-validate.wy-inline-validate-info .btn .wy-input-context, .btn .wy-tag-input-group .wy-tag .wy-tag-remove, .wy-tag-input-group .wy-tag .btn .wy-tag-remove, .btn .rst-content .admonition-title, .rst-content .btn .admonition-title, .btn .rst-content h1 .headerlink, .rst-content h1 .btn .headerlink, .btn .rst-content h2 .headerlink, .rst-content h2 .btn .headerlink, .btn .rst-content h3 .headerlink, .rst-content h3 .btn .headerlink, .btn .rst-content h4 .headerlink, .rst-content h4 .btn .headerlink, .btn .rst-content h5 .headerlink, .rst-content h5 .btn .headerlink, .btn .rst-content h6 .headerlink, .rst-content h6 .btn .headerlink, .btn .rst-content dl dt .headerlink, .rst-content dl dt .btn .headerlink, .nav .icon, .nav .wy-inline-validate.wy-inline-validate-success .wy-input-context, .wy-inline-validate.wy-inline-validate-success .nav .wy-input-context, .nav .wy-inline-validate.wy-inline-validate-danger .wy-input-context, .wy-inline-validate.wy-inline-validate-danger .nav .wy-input-context, .nav .wy-inline-validate.wy-inline-validate-warning .wy-input-context, .wy-inline-validate.wy-inline-validate-warning .nav .wy-input-context, .nav .wy-inline-validate.wy-inline-validate-info .wy-input-context, .wy-inline-validate.wy-inline-validate-info .nav .wy-input-context, .nav .wy-tag-input-group .wy-tag .wy-tag-remove, .wy-tag-input-group .wy-tag .nav .wy-tag-remove, .nav .rst-content .admonition-title, .rst-content .nav .admonition-title, .nav .rst-content h1 .headerlink, .rst-content h1 .nav .headerlink, .nav .rst-content h2 .headerlink, .rst-content h2 .nav .headerlink, .nav .rst-content h3 .headerlink, .rst-content h3 .nav .headerlink, .nav .rst-content h4 .headerlink, .rst-content h4 .nav .headerlink, .nav .rst-content h5 .headerlink, .rst-content h5 .nav .headerlink, .nav .rst-content h6 .headerlink, .rst-content h6 .nav .headerlink, .nav .rst-content dl dt .headerlink, .rst-content dl dt .nav .headerlink { + display: inline; +} + +.btn .icon.icon-large, .btn .wy-inline-validate.wy-inline-validate-success .icon-large.wy-input-context, .wy-inline-validate.wy-inline-validate-success .btn .icon-large.wy-input-context, .btn .wy-inline-validate.wy-inline-validate-danger .icon-large.wy-input-context, .wy-inline-validate.wy-inline-validate-danger .btn .icon-large.wy-input-context, .btn .wy-inline-validate.wy-inline-validate-warning .icon-large.wy-input-context, .wy-inline-validate.wy-inline-validate-warning .btn .icon-large.wy-input-context, .btn .wy-inline-validate.wy-inline-validate-info .icon-large.wy-input-context, .wy-inline-validate.wy-inline-validate-info .btn .icon-large.wy-input-context, .btn .wy-tag-input-group .wy-tag .icon-large.wy-tag-remove, .wy-tag-input-group .wy-tag .btn .icon-large.wy-tag-remove, .btn .rst-content .icon-large.admonition-title, .rst-content .btn .icon-large.admonition-title, .btn .rst-content h1 .icon-large.headerlink, .rst-content h1 .btn .icon-large.headerlink, .btn .rst-content h2 .icon-large.headerlink, .rst-content h2 .btn .icon-large.headerlink, .btn .rst-content h3 .icon-large.headerlink, .rst-content h3 .btn .icon-large.headerlink, .btn .rst-content h4 .icon-large.headerlink, .rst-content h4 .btn .icon-large.headerlink, .btn .rst-content h5 .icon-large.headerlink, .rst-content h5 .btn .icon-large.headerlink, .btn .rst-content h6 .icon-large.headerlink, .rst-content h6 .btn .icon-large.headerlink, .btn .rst-content dl dt .icon-large.headerlink, .rst-content dl dt .btn .icon-large.headerlink, .nav .icon.icon-large, .nav .wy-inline-validate.wy-inline-validate-success .icon-large.wy-input-context, .wy-inline-validate.wy-inline-validate-success .nav .icon-large.wy-input-context, .nav .wy-inline-validate.wy-inline-validate-danger .icon-large.wy-input-context, .wy-inline-validate.wy-inline-validate-danger .nav .icon-large.wy-input-context, .nav .wy-inline-validate.wy-inline-validate-warning .icon-large.wy-input-context, .wy-inline-validate.wy-inline-validate-warning .nav .icon-large.wy-input-context, .nav .wy-inline-validate.wy-inline-validate-info .icon-large.wy-input-context, .wy-inline-validate.wy-inline-validate-info .nav .icon-large.wy-input-context, .nav .wy-tag-input-group .wy-tag .icon-large.wy-tag-remove, .wy-tag-input-group .wy-tag .nav .icon-large.wy-tag-remove, .nav .rst-content .icon-large.admonition-title, .rst-content .nav .icon-large.admonition-title, .nav .rst-content h1 .icon-large.headerlink, .rst-content h1 .nav .icon-large.headerlink, .nav .rst-content h2 .icon-large.headerlink, .rst-content h2 .nav .icon-large.headerlink, .nav .rst-content h3 .icon-large.headerlink, .rst-content h3 .nav .icon-large.headerlink, .nav .rst-content h4 .icon-large.headerlink, .rst-content h4 .nav .icon-large.headerlink, .nav .rst-content h5 .icon-large.headerlink, .rst-content h5 .nav .icon-large.headerlink, .nav .rst-content h6 .icon-large.headerlink, .rst-content h6 .nav .icon-large.headerlink, .nav .rst-content dl dt .icon-large.headerlink, .rst-content dl dt .nav .icon-large.headerlink { + line-height: 0.9em; +} + +.btn .icon.icon-spin, .btn .wy-inline-validate.wy-inline-validate-success .icon-spin.wy-input-context, .wy-inline-validate.wy-inline-validate-success .btn .icon-spin.wy-input-context, .btn .wy-inline-validate.wy-inline-validate-danger .icon-spin.wy-input-context, .wy-inline-validate.wy-inline-validate-danger .btn .icon-spin.wy-input-context, .btn .wy-inline-validate.wy-inline-validate-warning .icon-spin.wy-input-context, .wy-inline-validate.wy-inline-validate-warning .btn .icon-spin.wy-input-context, .btn .wy-inline-validate.wy-inline-validate-info .icon-spin.wy-input-context, .wy-inline-validate.wy-inline-validate-info .btn .icon-spin.wy-input-context, .btn .wy-tag-input-group .wy-tag .icon-spin.wy-tag-remove, .wy-tag-input-group .wy-tag .btn .icon-spin.wy-tag-remove, .btn .rst-content .icon-spin.admonition-title, .rst-content .btn .icon-spin.admonition-title, .btn .rst-content h1 .icon-spin.headerlink, .rst-content h1 .btn .icon-spin.headerlink, .btn .rst-content h2 .icon-spin.headerlink, .rst-content h2 .btn .icon-spin.headerlink, .btn .rst-content h3 .icon-spin.headerlink, .rst-content h3 .btn .icon-spin.headerlink, .btn .rst-content h4 .icon-spin.headerlink, .rst-content h4 .btn .icon-spin.headerlink, .btn .rst-content h5 .icon-spin.headerlink, .rst-content h5 .btn .icon-spin.headerlink, .btn .rst-content h6 .icon-spin.headerlink, .rst-content h6 .btn .icon-spin.headerlink, .btn .rst-content dl dt .icon-spin.headerlink, .rst-content dl dt .btn .icon-spin.headerlink, .nav .icon.icon-spin, .nav .wy-inline-validate.wy-inline-validate-success .icon-spin.wy-input-context, .wy-inline-validate.wy-inline-validate-success .nav .icon-spin.wy-input-context, .nav .wy-inline-validate.wy-inline-validate-danger .icon-spin.wy-input-context, .wy-inline-validate.wy-inline-validate-danger .nav .icon-spin.wy-input-context, .nav .wy-inline-validate.wy-inline-validate-warning .icon-spin.wy-input-context, .wy-inline-validate.wy-inline-validate-warning .nav .icon-spin.wy-input-context, .nav .wy-inline-validate.wy-inline-validate-info .icon-spin.wy-input-context, .wy-inline-validate.wy-inline-validate-info .nav .icon-spin.wy-input-context, .nav .wy-tag-input-group .wy-tag .icon-spin.wy-tag-remove, .wy-tag-input-group .wy-tag .nav .icon-spin.wy-tag-remove, .nav .rst-content .icon-spin.admonition-title, .rst-content .nav .icon-spin.admonition-title, .nav .rst-content h1 .icon-spin.headerlink, .rst-content h1 .nav .icon-spin.headerlink, .nav .rst-content h2 .icon-spin.headerlink, .rst-content h2 .nav .icon-spin.headerlink, .nav .rst-content h3 .icon-spin.headerlink, .rst-content h3 .nav .icon-spin.headerlink, .nav .rst-content h4 .icon-spin.headerlink, .rst-content h4 .nav .icon-spin.headerlink, .nav .rst-content h5 .icon-spin.headerlink, .rst-content h5 .nav .icon-spin.headerlink, .nav .rst-content h6 .icon-spin.headerlink, .rst-content h6 .nav .icon-spin.headerlink, .nav .rst-content dl dt .icon-spin.headerlink, .rst-content dl dt .nav .icon-spin.headerlink { + display: inline-block; +} + +.btn.icon:before, .wy-inline-validate.wy-inline-validate-success .btn.wy-input-context:before, .wy-inline-validate.wy-inline-validate-danger .btn.wy-input-context:before, .wy-inline-validate.wy-inline-validate-warning .btn.wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .btn.wy-input-context:before, .wy-tag-input-group .wy-tag .btn.wy-tag-remove:before, .rst-content .btn.admonition-title:before, .rst-content h1 .btn.headerlink:before, .rst-content h2 .btn.headerlink:before, .rst-content h3 .btn.headerlink:before, .rst-content h4 .btn.headerlink:before, .rst-content h5 .btn.headerlink:before, .rst-content h6 .btn.headerlink:before, .rst-content dl dt .btn.headerlink:before { + opacity: 0.5; + -webkit-transition: opacity 0.05s ease-in; + -moz-transition: opacity 0.05s ease-in; + transition: opacity 0.05s ease-in; +} + +.btn.icon:hover:before, .wy-inline-validate.wy-inline-validate-success .btn.wy-input-context:hover:before, .wy-inline-validate.wy-inline-validate-danger .btn.wy-input-context:hover:before, .wy-inline-validate.wy-inline-validate-warning .btn.wy-input-context:hover:before, .wy-inline-validate.wy-inline-validate-info .btn.wy-input-context:hover:before, .wy-tag-input-group .wy-tag .btn.wy-tag-remove:hover:before, .rst-content .btn.admonition-title:hover:before, .rst-content h1 .btn.headerlink:hover:before, .rst-content h2 .btn.headerlink:hover:before, .rst-content h3 .btn.headerlink:hover:before, .rst-content h4 .btn.headerlink:hover:before, .rst-content h5 .btn.headerlink:hover:before, .rst-content h6 .btn.headerlink:hover:before, .rst-content dl dt .btn.headerlink:hover:before { + opacity: 1; +} + +.btn-mini .icon:before, .btn-mini .wy-inline-validate.wy-inline-validate-success .wy-input-context:before, .wy-inline-validate.wy-inline-validate-success .btn-mini .wy-input-context:before, .btn-mini .wy-inline-validate.wy-inline-validate-danger .wy-input-context:before, .wy-inline-validate.wy-inline-validate-danger .btn-mini .wy-input-context:before, .btn-mini .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, .wy-inline-validate.wy-inline-validate-warning .btn-mini .wy-input-context:before, .btn-mini .wy-inline-validate.wy-inline-validate-info .wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .btn-mini .wy-input-context:before, .btn-mini .wy-tag-input-group .wy-tag .wy-tag-remove:before, .wy-tag-input-group .wy-tag .btn-mini .wy-tag-remove:before, .btn-mini .rst-content .admonition-title:before, .rst-content .btn-mini .admonition-title:before, .btn-mini .rst-content h1 .headerlink:before, .rst-content h1 .btn-mini .headerlink:before, .btn-mini .rst-content h2 .headerlink:before, .rst-content h2 .btn-mini .headerlink:before, .btn-mini .rst-content h3 .headerlink:before, .rst-content h3 .btn-mini .headerlink:before, .btn-mini .rst-content h4 .headerlink:before, .rst-content h4 .btn-mini .headerlink:before, .btn-mini .rst-content h5 .headerlink:before, .rst-content h5 .btn-mini .headerlink:before, .btn-mini .rst-content h6 .headerlink:before, .rst-content h6 .btn-mini .headerlink:before, .btn-mini .rst-content dl dt .headerlink:before, .rst-content dl dt .btn-mini .headerlink:before { + font-size: 14px; + vertical-align: -15%; +} + +li .icon, li .wy-inline-validate.wy-inline-validate-success .wy-input-context, .wy-inline-validate.wy-inline-validate-success li .wy-input-context, li .wy-inline-validate.wy-inline-validate-danger .wy-input-context, .wy-inline-validate.wy-inline-validate-danger li .wy-input-context, li .wy-inline-validate.wy-inline-validate-warning .wy-input-context, .wy-inline-validate.wy-inline-validate-warning li .wy-input-context, li .wy-inline-validate.wy-inline-validate-info .wy-input-context, .wy-inline-validate.wy-inline-validate-info li .wy-input-context, li .wy-tag-input-group .wy-tag .wy-tag-remove, .wy-tag-input-group .wy-tag li .wy-tag-remove, li .rst-content .admonition-title, .rst-content li .admonition-title, li .rst-content h1 .headerlink, .rst-content h1 li .headerlink, li .rst-content h2 .headerlink, .rst-content h2 li .headerlink, li .rst-content h3 .headerlink, .rst-content h3 li .headerlink, li .rst-content h4 .headerlink, .rst-content h4 li .headerlink, li .rst-content h5 .headerlink, .rst-content h5 li .headerlink, li .rst-content h6 .headerlink, .rst-content h6 li .headerlink, li .rst-content dl dt .headerlink, .rst-content dl dt li .headerlink { + display: inline-block; +} + +li .icon-large:before, li .icon-large:before { + width: 1.875em; +} + +ul.icons { + list-style-type: none; + margin-left: 2em; + text-indent: -0.8em; +} + +ul.icons li .icon, ul.icons li .wy-inline-validate.wy-inline-validate-success .wy-input-context, .wy-inline-validate.wy-inline-validate-success ul.icons li .wy-input-context, ul.icons li .wy-inline-validate.wy-inline-validate-danger .wy-input-context, .wy-inline-validate.wy-inline-validate-danger ul.icons li .wy-input-context, ul.icons li .wy-inline-validate.wy-inline-validate-warning .wy-input-context, .wy-inline-validate.wy-inline-validate-warning ul.icons li .wy-input-context, ul.icons li .wy-inline-validate.wy-inline-validate-info .wy-input-context, .wy-inline-validate.wy-inline-validate-info ul.icons li .wy-input-context, ul.icons li .wy-tag-input-group .wy-tag .wy-tag-remove, .wy-tag-input-group .wy-tag ul.icons li .wy-tag-remove, ul.icons li .rst-content .admonition-title, .rst-content ul.icons li .admonition-title, ul.icons li .rst-content h1 .headerlink, .rst-content h1 ul.icons li .headerlink, ul.icons li .rst-content h2 .headerlink, .rst-content h2 ul.icons li .headerlink, ul.icons li .rst-content h3 .headerlink, .rst-content h3 ul.icons li .headerlink, ul.icons li .rst-content h4 .headerlink, .rst-content h4 ul.icons li .headerlink, ul.icons li .rst-content h5 .headerlink, .rst-content h5 ul.icons li .headerlink, ul.icons li .rst-content h6 .headerlink, .rst-content h6 ul.icons li .headerlink, ul.icons li .rst-content dl dt .headerlink, .rst-content dl dt ul.icons li .headerlink { + width: 0.8em; +} + +ul.icons li .icon-large:before, ul.icons li .icon-large:before { + vertical-align: baseline; +} + +.icon-glass:before { + content: "\f000"; +} + +.icon-music:before { + content: "\f001"; +} + +.icon-search:before { + content: "\f002"; +} + +.icon-envelope-alt:before { + content: "\f003"; +} + +.icon-heart:before { + content: "\f004"; +} + +.icon-star:before { + content: "\f005"; +} + +.icon-star-empty:before { + content: "\f006"; +} + +.icon-user:before { + content: "\f007"; +} + +.icon-film:before { + content: "\f008"; +} + +.icon-th-large:before { + content: "\f009"; +} + +.icon-th:before { + content: "\f00a"; +} + +.icon-th-list:before { + content: "\f00b"; +} + +.icon-ok:before { + content: "\f00c"; +} + +.icon-remove:before, .wy-tag-input-group .wy-tag .wy-tag-remove:before { + content: "\f00d"; +} + +.icon-zoom-in:before { + content: "\f00e"; +} + +.icon-zoom-out:before { + content: "\f010"; +} + +.icon-power-off:before, .icon-off:before { + content: "\f011"; +} + +.icon-signal:before { + content: "\f012"; +} + +.icon-gear:before, .icon-cog:before { + content: "\f013"; +} + +.icon-trash:before { + content: "\f014"; +} + +.icon-home:before { + content: "\f015"; +} + +.icon-file-alt:before { + content: "\f016"; +} + +.icon-time:before { + content: "\f017"; +} + +.icon-road:before { + content: "\f018"; +} + +.icon-download-alt:before { + content: "\f019"; +} + +.icon-download:before { + content: "\f01a"; +} + +.icon-upload:before { + content: "\f01b"; +} + +.icon-inbox:before { + content: "\f01c"; +} + +.icon-play-circle:before { + content: "\f01d"; +} + +.icon-rotate-right:before, .icon-repeat:before { + content: "\f01e"; +} + +.icon-refresh:before { + content: "\f021"; +} + +.icon-list-alt:before { + content: "\f022"; +} + +.icon-lock:before { + content: "\f023"; +} + +.icon-flag:before { + content: "\f024"; +} + +.icon-headphones:before { + content: "\f025"; +} + +.icon-volume-off:before { + content: "\f026"; +} + +.icon-volume-down:before { + content: "\f027"; +} + +.icon-volume-up:before { + content: "\f028"; +} + +.icon-qrcode:before { + content: "\f029"; +} + +.icon-barcode:before { + content: "\f02a"; +} + +.icon-tag:before { + content: "\f02b"; +} + +.icon-tags:before { + content: "\f02c"; +} + +.icon-book:before { + content: "\f02d"; +} + +.icon-bookmark:before { + content: "\f02e"; +} + +.icon-print:before { + content: "\f02f"; +} + +.icon-camera:before { + content: "\f030"; +} + +.icon-font:before { + content: "\f031"; +} + +.icon-bold:before { + content: "\f032"; +} + +.icon-italic:before { + content: "\f033"; +} + +.icon-text-height:before { + content: "\f034"; +} + +.icon-text-width:before { + content: "\f035"; +} + +.icon-align-left:before { + content: "\f036"; +} + +.icon-align-center:before { + content: "\f037"; +} + +.icon-align-right:before { + content: "\f038"; +} + +.icon-align-justify:before { + content: "\f039"; +} + +.icon-list:before { + content: "\f03a"; +} + +.icon-indent-left:before { + content: "\f03b"; +} + +.icon-indent-right:before { + content: "\f03c"; +} + +.icon-facetime-video:before { + content: "\f03d"; +} + +.icon-picture:before { + content: "\f03e"; +} + +.icon-pencil:before { + content: "\f040"; +} + +.icon-map-marker:before { + content: "\f041"; +} + +.icon-adjust:before { + content: "\f042"; +} + +.icon-tint:before { + content: "\f043"; +} + +.icon-edit:before { + content: "\f044"; +} + +.icon-share:before { + content: "\f045"; +} + +.icon-check:before { + content: "\f046"; +} + +.icon-move:before { + content: "\f047"; +} + +.icon-step-backward:before { + content: "\f048"; +} + +.icon-fast-backward:before { + content: "\f049"; +} + +.icon-backward:before { + content: "\f04a"; +} + +.icon-play:before { + content: "\f04b"; +} + +.icon-pause:before { + content: "\f04c"; +} + +.icon-stop:before { + content: "\f04d"; +} + +.icon-forward:before { + content: "\f04e"; +} + +.icon-fast-forward:before { + content: "\f050"; +} + +.icon-step-forward:before { + content: "\f051"; +} + +.icon-eject:before { + content: "\f052"; +} + +.icon-chevron-left:before { + content: "\f053"; +} + +.icon-chevron-right:before { + content: "\f054"; +} + +.icon-plus-sign:before { + content: "\f055"; +} + +.icon-minus-sign:before { + content: "\f056"; +} + +.icon-remove-sign:before, .wy-inline-validate.wy-inline-validate-danger .wy-input-context:before { + content: "\f057"; +} + +.icon-ok-sign:before { + content: "\f058"; +} + +.icon-question-sign:before { + content: "\f059"; +} + +.icon-info-sign:before { + content: "\f05a"; +} + +.icon-screenshot:before { + content: "\f05b"; +} + +.icon-remove-circle:before { + content: "\f05c"; +} + +.icon-ok-circle:before { + content: "\f05d"; +} + +.icon-ban-circle:before { + content: "\f05e"; +} + +.icon-arrow-left:before { + content: "\f060"; +} + +.icon-arrow-right:before { + content: "\f061"; +} + +.icon-arrow-up:before { + content: "\f062"; +} + +.icon-arrow-down:before { + content: "\f063"; +} + +.icon-mail-forward:before, .icon-share-alt:before { + content: "\f064"; +} + +.icon-resize-full:before { + content: "\f065"; +} + +.icon-resize-small:before { + content: "\f066"; +} + +.icon-plus:before { + content: "\f067"; +} + +.icon-minus:before { + content: "\f068"; +} + +.icon-asterisk:before { + content: "\f069"; +} + +.icon-exclamation-sign:before, .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .wy-input-context:before, .rst-content .admonition-title:before { + content: "\f06a"; +} + +.icon-gift:before { + content: "\f06b"; +} + +.icon-leaf:before { + content: "\f06c"; +} + +.icon-fire:before { + content: "\f06d"; +} + +.icon-eye-open:before { + content: "\f06e"; +} + +.icon-eye-close:before { + content: "\f070"; +} + +.icon-warning-sign:before { + content: "\f071"; +} + +.icon-plane:before { + content: "\f072"; +} + +.icon-calendar:before { + content: "\f073"; +} + +.icon-random:before { + content: "\f074"; +} + +.icon-comment:before { + content: "\f075"; +} + +.icon-magnet:before { + content: "\f076"; +} + +.icon-chevron-up:before { + content: "\f077"; +} + +.icon-chevron-down:before { + content: "\f078"; +} + +.icon-retweet:before { + content: "\f079"; +} + +.icon-shopping-cart:before { + content: "\f07a"; +} + +.icon-folder-close:before { + content: "\f07b"; +} + +.icon-folder-open:before { + content: "\f07c"; +} + +.icon-resize-vertical:before { + content: "\f07d"; +} + +.icon-resize-horizontal:before { + content: "\f07e"; +} + +.icon-bar-chart:before { + content: "\f080"; +} + +.icon-twitter-sign:before { + content: "\f081"; +} + +.icon-facebook-sign:before { + content: "\f082"; +} + +.icon-camera-retro:before { + content: "\f083"; +} + +.icon-key:before { + content: "\f084"; +} + +.icon-gears:before, .icon-cogs:before { + content: "\f085"; +} + +.icon-comments:before { + content: "\f086"; +} + +.icon-thumbs-up-alt:before { + content: "\f087"; +} + +.icon-thumbs-down-alt:before { + content: "\f088"; +} + +.icon-star-half:before { + content: "\f089"; +} + +.icon-heart-empty:before { + content: "\f08a"; +} + +.icon-signout:before { + content: "\f08b"; +} + +.icon-linkedin-sign:before { + content: "\f08c"; +} + +.icon-pushpin:before { + content: "\f08d"; +} + +.icon-external-link:before { + content: "\f08e"; +} + +.icon-signin:before { + content: "\f090"; +} + +.icon-trophy:before { + content: "\f091"; +} + +.icon-github-sign:before { + content: "\f092"; +} + +.icon-upload-alt:before { + content: "\f093"; +} + +.icon-lemon:before { + content: "\f094"; +} + +.icon-phone:before { + content: "\f095"; +} + +.icon-unchecked:before, .icon-check-empty:before { + content: "\f096"; +} + +.icon-bookmark-empty:before { + content: "\f097"; +} + +.icon-phone-sign:before { + content: "\f098"; +} + +.icon-twitter:before { + content: "\f099"; +} + +.icon-facebook:before { + content: "\f09a"; +} + +.icon-github:before { + content: "\f09b"; +} + +.icon-unlock:before { + content: "\f09c"; +} + +.icon-credit-card:before { + content: "\f09d"; +} + +.icon-rss:before { + content: "\f09e"; +} + +.icon-hdd:before { + content: "\f0a0"; +} + +.icon-bullhorn:before { + content: "\f0a1"; +} + +.icon-bell:before { + content: "\f0a2"; +} + +.icon-certificate:before { + content: "\f0a3"; +} + +.icon-hand-right:before { + content: "\f0a4"; +} + +.icon-hand-left:before { + content: "\f0a5"; +} + +.icon-hand-up:before { + content: "\f0a6"; +} + +.icon-hand-down:before { + content: "\f0a7"; +} + +.icon-circle-arrow-left:before { + content: "\f0a8"; +} + +.icon-circle-arrow-right:before { + content: "\f0a9"; +} + +.icon-circle-arrow-up:before { + content: "\f0aa"; +} + +.icon-circle-arrow-down:before { + content: "\f0ab"; +} + +.icon-globe:before { + content: "\f0ac"; +} + +.icon-wrench:before { + content: "\f0ad"; +} + +.icon-tasks:before { + content: "\f0ae"; +} + +.icon-filter:before { + content: "\f0b0"; +} + +.icon-briefcase:before { + content: "\f0b1"; +} + +.icon-fullscreen:before { + content: "\f0b2"; +} + +.icon-group:before { + content: "\f0c0"; +} + +.icon-link:before { + content: "\f0c1"; +} + +.icon-cloud:before { + content: "\f0c2"; +} + +.icon-beaker:before { + content: "\f0c3"; +} + +.icon-cut:before { + content: "\f0c4"; +} + +.icon-copy:before { + content: "\f0c5"; +} + +.icon-paperclip:before, .icon-paper-clip:before { + content: "\f0c6"; +} + +.icon-save:before { + content: "\f0c7"; +} + +.icon-sign-blank:before { + content: "\f0c8"; +} + +.icon-reorder:before { + content: "\f0c9"; +} + +.icon-list-ul:before { + content: "\f0ca"; +} + +.icon-list-ol:before { + content: "\f0cb"; +} + +.icon-strikethrough:before { + content: "\f0cc"; +} + +.icon-underline:before { + content: "\f0cd"; +} + +.icon-table:before { + content: "\f0ce"; +} + +.icon-magic:before { + content: "\f0d0"; +} + +.icon-truck:before { + content: "\f0d1"; +} + +.icon-pinterest:before { + content: "\f0d2"; +} + +.icon-pinterest-sign:before { + content: "\f0d3"; +} + +.icon-google-plus-sign:before { + content: "\f0d4"; +} + +.icon-google-plus:before { + content: "\f0d5"; +} + +.icon-money:before { + content: "\f0d6"; +} + +.icon-caret-down:before { + content: "\f0d7"; +} + +.icon-caret-up:before { + content: "\f0d8"; +} + +.icon-caret-left:before { + content: "\f0d9"; +} + +.icon-caret-right:before { + content: "\f0da"; +} + +.icon-columns:before { + content: "\f0db"; +} + +.icon-sort:before { + content: "\f0dc"; +} + +.icon-sort-down:before { + content: "\f0dd"; +} + +.icon-sort-up:before { + content: "\f0de"; +} + +.icon-envelope:before { + content: "\f0e0"; +} + +.icon-linkedin:before { + content: "\f0e1"; +} + +.icon-rotate-left:before, .icon-undo:before { + content: "\f0e2"; +} + +.icon-legal:before { + content: "\f0e3"; +} + +.icon-dashboard:before { + content: "\f0e4"; +} + +.icon-comment-alt:before { + content: "\f0e5"; +} + +.icon-comments-alt:before { + content: "\f0e6"; +} + +.icon-bolt:before { + content: "\f0e7"; +} + +.icon-sitemap:before { + content: "\f0e8"; +} + +.icon-umbrella:before { + content: "\f0e9"; +} + +.icon-paste:before { + content: "\f0ea"; +} + +.icon-lightbulb:before { + content: "\f0eb"; +} + +.icon-exchange:before { + content: "\f0ec"; +} + +.icon-cloud-download:before { + content: "\f0ed"; +} + +.icon-cloud-upload:before { + content: "\f0ee"; +} + +.icon-user-md:before { + content: "\f0f0"; +} + +.icon-stethoscope:before { + content: "\f0f1"; +} + +.icon-suitcase:before { + content: "\f0f2"; +} + +.icon-bell-alt:before { + content: "\f0f3"; +} + +.icon-coffee:before { + content: "\f0f4"; +} + +.icon-food:before { + content: "\f0f5"; +} + +.icon-file-text-alt:before { + content: "\f0f6"; +} + +.icon-building:before { + content: "\f0f7"; +} + +.icon-hospital:before { + content: "\f0f8"; +} + +.icon-ambulance:before { + content: "\f0f9"; +} + +.icon-medkit:before { + content: "\f0fa"; +} + +.icon-fighter-jet:before { + content: "\f0fb"; +} + +.icon-beer:before { + content: "\f0fc"; +} + +.icon-h-sign:before { + content: "\f0fd"; +} + +.icon-plus-sign-alt:before { + content: "\f0fe"; +} + +.icon-double-angle-left:before { + content: "\f100"; +} + +.icon-double-angle-right:before { + content: "\f101"; +} + +.icon-double-angle-up:before { + content: "\f102"; +} + +.icon-double-angle-down:before { + content: "\f103"; +} + +.icon-angle-left:before { + content: "\f104"; +} + +.icon-angle-right:before { + content: "\f105"; +} + +.icon-angle-up:before { + content: "\f106"; +} + +.icon-angle-down:before { + content: "\f107"; +} + +.icon-desktop:before { + content: "\f108"; +} + +.icon-laptop:before { + content: "\f109"; +} + +.icon-tablet:before { + content: "\f10a"; +} + +.icon-mobile-phone:before { + content: "\f10b"; +} + +.icon-circle-blank:before { + content: "\f10c"; +} + +.icon-quote-left:before { + content: "\f10d"; +} + +.icon-quote-right:before { + content: "\f10e"; +} + +.icon-spinner:before { + content: "\f110"; +} + +.icon-circle:before { + content: "\f111"; +} + +.icon-mail-reply:before, .icon-reply:before { + content: "\f112"; +} + +.icon-github-alt:before { + content: "\f113"; +} + +.icon-folder-close-alt:before { + content: "\f114"; +} + +.icon-folder-open-alt:before { + content: "\f115"; +} + +.icon-expand-alt:before { + content: "\f116"; +} + +.icon-collapse-alt:before { + content: "\f117"; +} + +.icon-smile:before { + content: "\f118"; +} + +.icon-frown:before { + content: "\f119"; +} + +.icon-meh:before { + content: "\f11a"; +} + +.icon-gamepad:before { + content: "\f11b"; +} + +.icon-keyboard:before { + content: "\f11c"; +} + +.icon-flag-alt:before { + content: "\f11d"; +} + +.icon-flag-checkered:before { + content: "\f11e"; +} + +.icon-terminal:before { + content: "\f120"; +} + +.icon-code:before { + content: "\f121"; +} + +.icon-reply-all:before { + content: "\f122"; +} + +.icon-mail-reply-all:before { + content: "\f122"; +} + +.icon-star-half-full:before, .icon-star-half-empty:before { + content: "\f123"; +} + +.icon-location-arrow:before { + content: "\f124"; +} + +.icon-crop:before { + content: "\f125"; +} + +.icon-code-fork:before { + content: "\f126"; +} + +.icon-unlink:before { + content: "\f127"; +} + +.icon-question:before { + content: "\f128"; +} + +.icon-info:before { + content: "\f129"; +} + +.icon-exclamation:before { + content: "\f12a"; +} + +.icon-superscript:before { + content: "\f12b"; +} + +.icon-subscript:before { + content: "\f12c"; +} + +.icon-eraser:before { + content: "\f12d"; +} + +.icon-puzzle-piece:before { + content: "\f12e"; +} + +.icon-microphone:before { + content: "\f130"; +} + +.icon-microphone-off:before { + content: "\f131"; +} + +.icon-shield:before { + content: "\f132"; +} + +.icon-calendar-empty:before { + content: "\f133"; +} + +.icon-fire-extinguisher:before { + content: "\f134"; +} + +.icon-rocket:before { + content: "\f135"; +} + +.icon-maxcdn:before { + content: "\f136"; +} + +.icon-chevron-sign-left:before { + content: "\f137"; +} + +.icon-chevron-sign-right:before { + content: "\f138"; +} + +.icon-chevron-sign-up:before { + content: "\f139"; +} + +.icon-chevron-sign-down:before { + content: "\f13a"; +} + +.icon-html5:before { + content: "\f13b"; +} + +.icon-css3:before { + content: "\f13c"; +} + +.icon-anchor:before { + content: "\f13d"; +} + +.icon-unlock-alt:before { + content: "\f13e"; +} + +.icon-bullseye:before { + content: "\f140"; +} + +.icon-ellipsis-horizontal:before { + content: "\f141"; +} + +.icon-ellipsis-vertical:before { + content: "\f142"; +} + +.icon-rss-sign:before { + content: "\f143"; +} + +.icon-play-sign:before { + content: "\f144"; +} + +.icon-ticket:before { + content: "\f145"; +} + +.icon-minus-sign-alt:before { + content: "\f146"; +} + +.icon-check-minus:before { + content: "\f147"; +} + +.icon-level-up:before { + content: "\f148"; +} + +.icon-level-down:before { + content: "\f149"; +} + +.icon-check-sign:before, .wy-inline-validate.wy-inline-validate-success .wy-input-context:before { + content: "\f14a"; +} + +.icon-edit-sign:before { + content: "\f14b"; +} + +.icon-external-link-sign:before { + content: "\f14c"; +} + +.icon-share-sign:before { + content: "\f14d"; +} + +.icon-compass:before { + content: "\f14e"; +} + +.icon-collapse:before { + content: "\f150"; +} + +.icon-collapse-top:before { + content: "\f151"; +} + +.icon-expand:before { + content: "\f152"; +} + +.icon-euro:before, .icon-eur:before { + content: "\f153"; +} + +.icon-gbp:before { + content: "\f154"; +} + +.icon-dollar:before, .icon-usd:before { + content: "\f155"; +} + +.icon-rupee:before, .icon-inr:before { + content: "\f156"; +} + +.icon-yen:before, .icon-jpy:before { + content: "\f157"; +} + +.icon-renminbi:before, .icon-cny:before { + content: "\f158"; +} + +.icon-won:before, .icon-krw:before { + content: "\f159"; +} + +.icon-bitcoin:before, .icon-btc:before { + content: "\f15a"; +} + +.icon-file:before { + content: "\f15b"; +} + +.icon-file-text:before { + content: "\f15c"; +} + +.icon-sort-by-alphabet:before { + content: "\f15d"; +} + +.icon-sort-by-alphabet-alt:before { + content: "\f15e"; +} + +.icon-sort-by-attributes:before { + content: "\f160"; +} + +.icon-sort-by-attributes-alt:before { + content: "\f161"; +} + +.icon-sort-by-order:before { + content: "\f162"; +} + +.icon-sort-by-order-alt:before { + content: "\f163"; +} + +.icon-thumbs-up:before { + content: "\f164"; +} + +.icon-thumbs-down:before { + content: "\f165"; +} + +.icon-youtube-sign:before { + content: "\f166"; +} + +.icon-youtube:before { + content: "\f167"; +} + +.icon-xing:before { + content: "\f168"; +} + +.icon-xing-sign:before { + content: "\f169"; +} + +.icon-youtube-play:before { + content: "\f16a"; +} + +.icon-dropbox:before { + content: "\f16b"; +} + +.icon-stackexchange:before { + content: "\f16c"; +} + +.icon-instagram:before { + content: "\f16d"; +} + +.icon-flickr:before { + content: "\f16e"; +} + +.icon-adn:before { + content: "\f170"; +} + +.icon-bitbucket:before { + content: "\f171"; +} + +.icon-bitbucket-sign:before { + content: "\f172"; +} + +.icon-tumblr:before { + content: "\f173"; +} + +.icon-tumblr-sign:before { + content: "\f174"; +} + +.icon-long-arrow-down:before { + content: "\f175"; +} + +.icon-long-arrow-up:before { + content: "\f176"; +} + +.icon-long-arrow-left:before { + content: "\f177"; +} + +.icon-long-arrow-right:before { + content: "\f178"; +} + +.icon-apple:before { + content: "\f179"; +} + +.icon-windows:before { + content: "\f17a"; +} + +.icon-android:before { + content: "\f17b"; +} + +.icon-linux:before { + content: "\f17c"; +} + +.icon-dribbble:before { + content: "\f17d"; +} + +.icon-skype:before { + content: "\f17e"; +} + +.icon-foursquare:before { + content: "\f180"; +} + +.icon-trello:before { + content: "\f181"; +} + +.icon-female:before { + content: "\f182"; +} + +.icon-male:before { + content: "\f183"; +} + +.icon-gittip:before { + content: "\f184"; +} + +.icon-sun:before { + content: "\f185"; +} + +.icon-moon:before { + content: "\f186"; +} + +.icon-archive:before { + content: "\f187"; +} + +.icon-bug:before { + content: "\f188"; +} + +.icon-vk:before { + content: "\f189"; +} + +.icon-weibo:before { + content: "\f18a"; +} + +.icon-renren:before { + content: "\f18b"; +} + +.wy-alert, .rst-content .note, .rst-content .attention, .rst-content .caution, .rst-content .danger, .rst-content .error, .rst-content .hint, .rst-content .important, .rst-content .tip, .rst-content .warning { + padding: 24px; + line-height: 24px; + margin-bottom: 24px; + border-left: solid 3px transparent; +} + +.wy-alert strong, .rst-content .note strong, .rst-content .attention strong, .rst-content .caution strong, .rst-content .danger strong, .rst-content .error strong, .rst-content .hint strong, .rst-content .important strong, .rst-content .tip strong, .rst-content .warning strong, .wy-alert a, .rst-content .note a, .rst-content .attention a, .rst-content .caution a, .rst-content .danger a, .rst-content .error a, .rst-content .hint a, .rst-content .important a, .rst-content .tip a, .rst-content .warning a { + color: #fff; +} + +.wy-alert.wy-alert-danger, .rst-content .wy-alert-danger.note, .rst-content .wy-alert-danger.attention, .rst-content .wy-alert-danger.caution, .rst-content .danger, .rst-content .error, .rst-content .wy-alert-danger.hint, .rst-content .wy-alert-danger.important, .rst-content .wy-alert-danger.tip, .rst-content .wy-alert-danger.warning { + background: #e74c3c; + color: #fff; + border-color: #d62c1a; +} + +.wy-alert.wy-alert-warning, .rst-content .wy-alert-warning.note, .rst-content .attention, .rst-content .caution, .rst-content .wy-alert-warning.danger, .rst-content .wy-alert-warning.error, .rst-content .wy-alert-warning.hint, .rst-content .wy-alert-warning.important, .rst-content .wy-alert-warning.tip, .rst-content .warning { + background: #e67e22; + color: #fff; + border-color: #bf6516; +} + +.wy-alert.wy-alert-info, .rst-content .note, .rst-content .wy-alert-info.attention, .rst-content .wy-alert-info.caution, .rst-content .wy-alert-info.danger, .rst-content .wy-alert-info.error, .rst-content .hint, .rst-content .important, .rst-content .tip, .rst-content .wy-alert-info.warning { + background: #2980b9; + color: #fff; + border-color: #20638f; +} + +.wy-alert.wy-alert-success, .rst-content .wy-alert-success.note, .rst-content .wy-alert-success.attention, .rst-content .wy-alert-success.caution, .rst-content .wy-alert-success.danger, .rst-content .wy-alert-success.error, .rst-content .wy-alert-success.hint, .rst-content .wy-alert-success.important, .rst-content .wy-alert-success.tip, .rst-content .wy-alert-success.warning { + background: #27ae60; + color: #fff; + border-color: #1e8449; +} + +.wy-alert.wy-alert-neutral, .rst-content .wy-alert-neutral.note, .rst-content .wy-alert-neutral.attention, .rst-content .wy-alert-neutral.caution, .rst-content .wy-alert-neutral.danger, .rst-content .wy-alert-neutral.error, .rst-content .wy-alert-neutral.hint, .rst-content .wy-alert-neutral.important, .rst-content .wy-alert-neutral.tip, .rst-content .wy-alert-neutral.warning { + background: #f3f6f6; + border-color: #e1e4e5; +} + +.wy-alert.wy-alert-neutral strong, .rst-content .wy-alert-neutral.note strong, .rst-content .wy-alert-neutral.attention strong, .rst-content .wy-alert-neutral.caution strong, .rst-content .wy-alert-neutral.danger strong, .rst-content .wy-alert-neutral.error strong, .rst-content .wy-alert-neutral.hint strong, .rst-content .wy-alert-neutral.important strong, .rst-content .wy-alert-neutral.tip strong, .rst-content .wy-alert-neutral.warning strong { + color: #404040; +} + +.wy-alert.wy-alert-neutral a, .rst-content .wy-alert-neutral.note a, .rst-content .wy-alert-neutral.attention a, .rst-content .wy-alert-neutral.caution a, .rst-content .wy-alert-neutral.danger a, .rst-content .wy-alert-neutral.error a, .rst-content .wy-alert-neutral.hint a, .rst-content .wy-alert-neutral.important a, .rst-content .wy-alert-neutral.tip a, .rst-content .wy-alert-neutral.warning a { + color: #2980b9; +} + +.wy-tray-container { + position: fixed; + top: -50px; + left: 0; + width: 100%; + -webkit-transition: top 0.2s ease-in; + -moz-transition: top 0.2s ease-in; + transition: top 0.2s ease-in; +} + +.wy-tray-container.on { + top: 0; +} + +.wy-tray-container li { + display: none; + width: 100%; + background: #343131; + padding: 12px 24px; + color: #fff; + margin-bottom: 6px; + text-align: center; + box-shadow: 0 5px 5px 0 rgba(0, 0, 0, 0.1), 0px -1px 2px -1px rgba(255, 255, 255, 0.5) inset; +} + +.wy-tray-container li.wy-tray-item-success { + background: #27ae60; +} + +.wy-tray-container li.wy-tray-item-info { + background: #2980b9; +} + +.wy-tray-container li.wy-tray-item-warning { + background: #e67e22; +} + +.wy-tray-container li.wy-tray-item-danger { + background: #e74c3c; +} + +.btn { + display: inline-block; + *display: inline; + zoom: 1; + line-height: normal; + white-space: nowrap; + vertical-align: baseline; + text-align: center; + cursor: pointer; + -webkit-user-drag: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + font-size: 100%; + padding: 6px 12px; + color: #fff; + border: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: solid 3px rgba(0, 0, 0, 0.1); + background-color: #27ae60; + text-decoration: none; + font-weight: 500; + box-shadow: 0px 1px 2px -1px rgba(255, 255, 255, 0.5) inset; + -webkit-transition: all 0.1s linear; + -moz-transition: all 0.1s linear; + transition: all 0.1s linear; + outline-none: false; +} + +.btn-hover { + background: #2e8ece; + color: #fff; +} + +.btn:hover { + background: #2cc36b; + color: #fff; +} + +.btn:focus { + background: #2cc36b; + color: #fff; + outline: 0; +} + +.btn:active { + border-top: solid 3px rgba(0, 0, 0, 0.1); + border-bottom: solid 1px rgba(0, 0, 0, 0.1); + box-shadow: 0px 1px 2px -1px rgba(0, 0, 0, 0.5) inset; +} + +.btn[disabled] { + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + filter: alpha(opacity=40); + opacity: 0.4; + cursor: not-allowed; + box-shadow: none; +} + +.btn-disabled { + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + filter: alpha(opacity=40); + opacity: 0.4; + cursor: not-allowed; + box-shadow: none; +} + +.btn-disabled:hover, .btn-disabled:focus, .btn-disabled:active { + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + filter: alpha(opacity=40); + opacity: 0.4; + cursor: not-allowed; + box-shadow: none; +} + +.btn::-moz-focus-inner { + padding: 0; + border: 0; +} + +.btn-small { + font-size: 80%; +} + +.btn-info { + background-color: #2980b9 !important; +} + +.btn-info:hover { + background-color: #2e8ece !important; +} + +.btn-neutral { + background-color: #f3f6f6 !important; + color: #404040 !important; +} + +.btn-neutral:hover { + background-color: #e5ebeb !important; + color: #404040; +} + +.btn-danger { + background-color: #e74c3c !important; +} + +.btn-danger:hover { + background-color: #ea6153 !important; +} + +.btn-warning { + background-color: #e67e22 !important; +} + +.btn-warning:hover { + background-color: #e98b39 !important; +} + +.btn-invert { + background-color: #343131; +} + +.btn-invert:hover { + background-color: #413d3d !important; +} + +.btn-link { + background-color: transparent !important; + color: #2980b9; + border-color: transparent; +} + +.btn-link:hover { + background-color: transparent !important; + color: #409ad5; + border-color: transparent; +} + +.btn-link:active { + background-color: transparent !important; + border-color: transparent; + border-top: solid 1px transparent; + border-bottom: solid 3px transparent; +} + +.wy-btn-group .btn, .wy-control .btn { + vertical-align: middle; +} + +.wy-btn-group { + margin-bottom: 24px; + *zoom: 1; +} + +.wy-btn-group:before, .wy-btn-group:after { + display: table; + content: ""; +} + +.wy-btn-group:after { + clear: both; +} + +.wy-dropdown { + position: relative; + display: inline-block; +} + +.wy-dropdown:hover .wy-dropdown-menu { + display: block; +} + +.wy-dropdown .caret:after { + font-family: fontawesome-webfont; + content: "\f0d7"; + font-size: 70%; +} + +.wy-dropdown-menu { + position: absolute; + top: 100%; + left: 0; + display: none; + float: left; + min-width: 100%; + background: #fcfcfc; + z-index: 100; + border: solid 1px #cfd7dd; + box-shadow: 0 5px 5px 0 rgba(0, 0, 0, 0.1); + padding: 12px; +} + +.wy-dropdown-menu>dd>a { + display: block; + clear: both; + color: #404040; + white-space: nowrap; + font-size: 90%; + padding: 0 12px; +} + +.wy-dropdown-menu>dd>a:hover { + background: #2980b9; + color: #fff; +} + +.wy-dropdown-menu>dd.divider { + border-top: solid 1px #cfd7dd; + margin: 6px 0; +} + +.wy-dropdown-menu>dd.search { + padding-bottom: 12px; +} + +.wy-dropdown-menu>dd.search input[type="search"] { + width: 100%; +} + +.wy-dropdown-menu>dd.call-to-action { + background: #e3e3e3; + text-transform: uppercase; + font-weight: 500; + font-size: 80%; +} + +.wy-dropdown-menu>dd.call-to-action:hover { + background: #e3e3e3; +} + +.wy-dropdown-menu>dd.call-to-action .btn { + color: #fff; +} + +.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu { + background: #fcfcfc; + margin-top: 2px; +} + +.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a { + padding: 6px 12px; +} + +.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover { + background: #2980b9; + color: #fff; +} + +.wy-dropdown.wy-dropdown-left .wy-dropdown-menu { + right: 0; + text-align: right; +} + +.wy-dropdown-arrow:before { + content: " "; + border-bottom: 5px solid #f5f5f5; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + position: absolute; + display: block; + top: -4px; + left: 50%; + margin-left: -3px; +} + +.wy-dropdown-arrow.wy-dropdown-arrow-left:before { + left: 11px; +} + +.wy-form-stacked select { + display: block; +} + +.wy-form-aligned input, .wy-form-aligned textarea, .wy-form-aligned select, .wy-form-aligned .wy-help-inline, .wy-form-aligned label { + display: inline-block; + *display: inline; + *zoom: 1; + vertical-align: middle; +} + +.wy-form-aligned .wy-control-group>label { + display: inline-block; + vertical-align: middle; + width: 10em; + margin: 0.5em 1em 0 0; + float: left; +} + +.wy-form-aligned .wy-control { + float: left; +} + +.wy-form-aligned .wy-control label { + display: block; +} + +.wy-form-aligned .wy-control select { + margin-top: 0.5em; +} + +fieldset { + border: 0; + margin: 0; + padding: 0; +} + +legend { + display: block; + width: 100%; + border: 0; + padding: 0; + white-space: normal; + margin-bottom: 24px; + font-size: 150%; + *margin-left: -7px; +} + +label { + display: block; + margin: 0 0 0.3125em 0; + color: #999; + font-size: 90%; +} + +button, input, select, textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; + *vertical-align: middle; +} + +button, input { + line-height: normal; +} + +button { + -webkit-appearance: button; + cursor: pointer; + *overflow: visible; +} + +button::-moz-focus-inner, input::-moz-focus-inner { + border: 0; + padding: 0; +} + +button[disabled] { + cursor: default; +} + +input[type="button"], input[type="reset"], input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; + *overflow: visible; +} + +input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"] { + -webkit-appearance: none; + padding: 6px; + display: inline-block; + border: 1px solid #ccc; + font-size: 80%; + font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; + box-shadow: inset 0 1px 3px #ddd; + border-radius: 0; + -webkit-transition: border 0.3s linear; + -moz-transition: border 0.3s linear; + transition: border 0.3s linear; +} + +input[type="datetime-local"] { + padding: 0.34375em 0.625em; +} + +input[disabled] { + cursor: default; +} + +input[type="checkbox"], input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; + margin-right: 0.3125em; + *height: 13px; + *width: 13px; +} + +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus { + outline: 0; + outline: thin dotted \9; + border-color: #2980b9; +} + +input.no-focus:focus { + border-color: #ccc !important; +} + +input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { + outline: thin dotted #333; + outline: 1px auto #129fea; +} + +input[type="text"][disabled], input[type="password"][disabled], input[type="email"][disabled], input[type="url"][disabled], input[type="date"][disabled], input[type="month"][disabled], input[type="time"][disabled], input[type="datetime"][disabled], input[type="datetime-local"][disabled], input[type="week"][disabled], input[type="number"][disabled], input[type="search"][disabled], input[type="tel"][disabled], input[type="color"][disabled] { + cursor: not-allowed; + background-color: #f3f6f6; + color: #cad2d3; +} + +input:focus:invalid, textarea:focus:invalid, select:focus:invalid { + color: #e74c3c; + border: 1px solid #e74c3c; +} + +input:focus:invalid:focus, textarea:focus:invalid:focus, select:focus:invalid:focus { + border-color: #e9322d; +} + +input[type="file"]:focus:invalid:focus, input[type="radio"]:focus:invalid:focus, input[type="checkbox"]:focus:invalid:focus { + outline-color: #e9322d; +} + +input.wy-input-large { + padding: 12px; + font-size: 100%; +} + +textarea { + overflow: auto; + vertical-align: top; + width: 100%; +} + +select, textarea { + padding: 0.5em 0.625em; + display: inline-block; + border: 1px solid #ccc; + font-size: 0.8em; + box-shadow: inset 0 1px 3px #ddd; + -webkit-transition: border 0.3s linear; + -moz-transition: border 0.3s linear; + transition: border 0.3s linear; +} + +select { + border: 1px solid #ccc; + background-color: #fff; +} + +select[multiple] { + height: auto; +} + +select:focus, textarea:focus { + outline: 0; +} + +select[disabled], textarea[disabled], input[readonly], select[readonly], textarea[readonly] { + cursor: not-allowed; + background-color: #fff; + color: #cad2d3; + border-color: transparent; +} + +.wy-checkbox, .wy-radio { + margin: 0.5em 0; + color: #404040 !important; + display: block; +} + +.wy-form-message-inline { + display: inline-block; + *display: inline; + *zoom: 1; + vertical-align: middle; +} + +.wy-input-prefix, .wy-input-suffix { + white-space: nowrap; +} + +.wy-input-prefix .wy-input-context, .wy-input-suffix .wy-input-context { + padding: 6px; + display: inline-block; + font-size: 80%; + background-color: #f3f6f6; + border: solid 1px #ccc; + color: #999; +} + +.wy-input-suffix .wy-input-context { + border-left: 0; +} + +.wy-input-prefix .wy-input-context { + border-right: 0; +} + +.wy-inline-validate { + white-space: nowrap; +} + +.wy-inline-validate .wy-input-context { + padding: 0.5em 0.625em; + display: inline-block; + font-size: 80%; +} + +.wy-inline-validate.wy-inline-validate-success .wy-input-context { + color: #27ae60; +} + +.wy-inline-validate.wy-inline-validate-danger .wy-input-context { + color: #e74c3c; +} + +.wy-inline-validate.wy-inline-validate-warning .wy-input-context { + color: #e67e22; +} + +.wy-inline-validate.wy-inline-validate-info .wy-input-context { + color: #2980b9; +} + +.wy-control-group { + margin-bottom: 24px; + *zoom: 1; +} + +.wy-control-group:before, .wy-control-group:after { + display: table; + content: ""; +} + +.wy-control-group:after { + clear: both; +} + +.wy-control-group.wy-control-group-error .wy-form-message, .wy-control-group.wy-control-group-error label { + color: #e74c3c; +} + +.wy-control-group.wy-control-group-error input[type="text"], .wy-control-group.wy-control-group-error input[type="password"], .wy-control-group.wy-control-group-error input[type="email"], .wy-control-group.wy-control-group-error input[type="url"], .wy-control-group.wy-control-group-error input[type="date"], .wy-control-group.wy-control-group-error input[type="month"], .wy-control-group.wy-control-group-error input[type="time"], .wy-control-group.wy-control-group-error input[type="datetime"], .wy-control-group.wy-control-group-error input[type="datetime-local"], .wy-control-group.wy-control-group-error input[type="week"], .wy-control-group.wy-control-group-error input[type="number"], .wy-control-group.wy-control-group-error input[type="search"], .wy-control-group.wy-control-group-error input[type="tel"], .wy-control-group.wy-control-group-error input[type="color"] { + border: solid 2px #e74c3c; +} + +.wy-control-group.wy-control-group-error textarea { + border: solid 2px #e74c3c; +} + +.wy-control-group.fluid-input input[type="text"], .wy-control-group.fluid-input input[type="password"], .wy-control-group.fluid-input input[type="email"], .wy-control-group.fluid-input input[type="url"], .wy-control-group.fluid-input input[type="date"], .wy-control-group.fluid-input input[type="month"], .wy-control-group.fluid-input input[type="time"], .wy-control-group.fluid-input input[type="datetime"], .wy-control-group.fluid-input input[type="datetime-local"], .wy-control-group.fluid-input input[type="week"], .wy-control-group.fluid-input input[type="number"], .wy-control-group.fluid-input input[type="search"], .wy-control-group.fluid-input input[type="tel"], .wy-control-group.fluid-input input[type="color"] { + width: 100%; +} + +.wy-form-message-inline { + display: inline-block; + padding-left: 0.3em; + color: #666; + vertical-align: middle; + font-size: 90%; +} + +.wy-form-message { + display: block; + color: #ccc; + font-size: 70%; + margin-top: 0.3125em; + font-style: italic; +} + +.wy-tag-input-group { + padding: 4px 4px 0px 4px; + display: inline-block; + border: 1px solid #ccc; + font-size: 80%; + font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; + box-shadow: inset 0 1px 3px #ddd; + -webkit-transition: border 0.3s linear; + -moz-transition: border 0.3s linear; + transition: border 0.3s linear; +} + +.wy-tag-input-group .wy-tag { + display: inline-block; + background-color: rgba(0, 0, 0, 0.1); + padding: 0.5em 0.625em; + border-radius: 2px; + position: relative; + margin-bottom: 4px; +} + +.wy-tag-input-group .wy-tag .wy-tag-remove { + color: #ccc; + margin-left: 5px; +} + +.wy-tag-input-group .wy-tag .wy-tag-remove:hover { + color: #e74c3c; +} + +.wy-tag-input-group label { + margin-left: 5px; + display: inline-block; + margin-bottom: 0; +} + +.wy-tag-input-group input { + border: none; + font-size: 100%; + margin-bottom: 4px; + box-shadow: none; +} + +.wy-form-upload { + border: solid 1px #ccc; + border-bottom: solid 3px #ccc; + background-color: #fff; + padding: 24px; + display: inline-block; + text-align: center; + cursor: pointer; + color: #404040; + -webkit-transition: border-color 0.1s ease-in; + -moz-transition: border-color 0.1s ease-in; + transition: border-color 0.1s ease-in; + *zoom: 1; +} + +.wy-form-upload:before, .wy-form-upload:after { + display: table; + content: ""; +} + +.wy-form-upload:after { + clear: both; +} + +@media screen and (max-width: 480px) { + .wy-form-upload { + width: 100%; + } +} + +.wy-form-upload .image-drop { + display: none; +} + +.wy-form-upload .image-desktop { + display: none; +} + +.wy-form-upload .image-loading { + display: none; +} + +.wy-form-upload .wy-form-upload-icon { + display: block; + font-size: 32px; + color: #b3b3b3; +} + +.wy-form-upload .image-drop .wy-form-upload-icon { + color: #27ae60; +} + +.wy-form-upload p { + font-size: 90%; +} + +.wy-form-upload .wy-form-upload-image { + float: left; + margin-right: 24px; +} + +@media screen and (max-width: 480px) { + .wy-form-upload .wy-form-upload-image { + width: 100%; + margin-bottom: 24px; + } +} + +.wy-form-upload img { + max-width: 125px; + max-height: 125px; + opacity: 0.9; + -webkit-transition: opacity 0.1s ease-in; + -moz-transition: opacity 0.1s ease-in; + transition: opacity 0.1s ease-in; +} + +.wy-form-upload .wy-form-upload-content { + float: left; +} + +@media screen and (max-width: 480px) { + .wy-form-upload .wy-form-upload-content { + width: 100%; + } +} + +.wy-form-upload:hover { + border-color: #b3b3b3; + color: #404040; +} + +.wy-form-upload:hover .image-desktop { + display: block; +} + +.wy-form-upload:hover .image-drag { + display: none; +} + +.wy-form-upload:hover img { + opacity: 1; +} + +.wy-form-upload:active { + border-top: solid 3px #ccc; + border-bottom: solid 1px #ccc; +} + +.wy-form-upload.wy-form-upload-big { + width: 100%; + text-align: center; + padding: 72px; +} + +.wy-form-upload.wy-form-upload-big .wy-form-upload-content { + float: none; +} + +.wy-form-upload.wy-form-upload-file p { + margin-bottom: 0; +} + +.wy-form-upload.wy-form-upload-file .wy-form-upload-icon { + display: inline-block; + font-size: inherit; +} + +.wy-form-upload.wy-form-upload-drop { + background-color: #ddf7e8; +} + +.wy-form-upload.wy-form-upload-drop .image-drop { + display: block; +} + +.wy-form-upload.wy-form-upload-drop .image-desktop { + display: none; +} + +.wy-form-upload.wy-form-upload-drop .image-drag { + display: none; +} + +.wy-form-upload.wy-form-upload-loading .image-drag { + display: none; +} + +.wy-form-upload.wy-form-upload-loading .image-desktop { + display: none; +} + +.wy-form-upload.wy-form-upload-loading .image-loading { + display: block; +} + +.wy-form-upload.wy-form-upload-loading .wy-input-prefix { + display: none; +} + +.wy-form-upload.wy-form-upload-loading p { + margin-bottom: 0; +} + +.rotate-90 { + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); + -o-transform: rotate(90deg); + transform: rotate(90deg); +} + +.rotate-180 { + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -ms-transform: rotate(180deg); + -o-transform: rotate(180deg); + transform: rotate(180deg); +} + +.rotate-270 { + -webkit-transform: rotate(270deg); + -moz-transform: rotate(270deg); + -ms-transform: rotate(270deg); + -o-transform: rotate(270deg); + transform: rotate(270deg); +} + +.mirror { + -webkit-transform: scaleX(-1); + -moz-transform: scaleX(-1); + -ms-transform: scaleX(-1); + -o-transform: scaleX(-1); + transform: scaleX(-1); +} + +.mirror.rotate-90 { + -webkit-transform: scaleX(-1) rotate(90deg); + -moz-transform: scaleX(-1) rotate(90deg); + -ms-transform: scaleX(-1) rotate(90deg); + -o-transform: scaleX(-1) rotate(90deg); + transform: scaleX(-1) rotate(90deg); +} + +.mirror.rotate-180 { + -webkit-transform: scaleX(-1) rotate(180deg); + -moz-transform: scaleX(-1) rotate(180deg); + -ms-transform: scaleX(-1) rotate(180deg); + -o-transform: scaleX(-1) rotate(180deg); + transform: scaleX(-1) rotate(180deg); +} + +.mirror.rotate-270 { + -webkit-transform: scaleX(-1) rotate(270deg); + -moz-transform: scaleX(-1) rotate(270deg); + -ms-transform: scaleX(-1) rotate(270deg); + -o-transform: scaleX(-1) rotate(270deg); + transform: scaleX(-1) rotate(270deg); +} + +.wy-form-gallery-manage { + margin-left: -12px; + margin-right: -12px; +} + +.wy-form-gallery-manage li { + float: left; + padding: 12px; + width: 20%; + cursor: pointer; +} + +@media screen and (max-width: 768px) { + .wy-form-gallery-manage li { + width: 25%; + } +} + +@media screen and (max-width: 480px) { + .wy-form-gallery-manage li { + width: 50%; + } +} + +.wy-form-gallery-manage li:active { + cursor: move; +} + +.wy-form-gallery-manage li>a { + padding: 12px; + background-color: #fff; + border: solid 1px #e1e4e5; + border-bottom: solid 3px #e1e4e5; + display: inline-block; + -webkit-transition: all 0.1s ease-in; + -moz-transition: all 0.1s ease-in; + transition: all 0.1s ease-in; +} + +.wy-form-gallery-manage li>a:active { + border: solid 1px #ccc; + border-top: solid 3px #ccc; +} + +.wy-form-gallery-manage img { + width: 100%; + -webkit-transition: all 0.05s ease-in; + -moz-transition: all 0.05s ease-in; + transition: all 0.05s ease-in; +} + +li.wy-form-gallery-edit { + position: relative; + color: #fff; + padding: 24px; + width: 100%; + display: block; + background-color: #343131; + border-radius: 4px; +} + +li.wy-form-gallery-edit .arrow { + position: absolute; + display: block; + top: -50px; + left: 50%; + margin-left: -25px; + z-index: 500; + height: 0; + width: 0; + border-color: transparent; + border-style: solid; + border-width: 25px; + border-bottom-color: #343131; +} + +@media only screen and (max-width: 480px) { + .wy-form button[type="submit"] { + margin: 0.7em 0 0; + } + + .wy-form input[type="text"], .wy-form input[type="password"], .wy-form input[type="email"], .wy-form input[type="url"], .wy-form input[type="date"], .wy-form input[type="month"], .wy-form input[type="time"], .wy-form input[type="datetime"], .wy-form input[type="datetime-local"], .wy-form input[type="week"], .wy-form input[type="number"], .wy-form input[type="search"], .wy-form input[type="tel"], .wy-form input[type="color"] { + margin-bottom: 0.3em; + display: block; + } + + .wy-form label { + margin-bottom: 0.3em; + display: block; + } + + .wy-form input[type="password"], .wy-form input[type="email"], .wy-form input[type="url"], .wy-form input[type="date"], .wy-form input[type="month"], .wy-form input[type="time"], .wy-form input[type="datetime"], .wy-form input[type="datetime-local"], .wy-form input[type="week"], .wy-form input[type="number"], .wy-form input[type="search"], .wy-form input[type="tel"], .wy-form input[type="color"] { + margin-bottom: 0; + } + + .wy-form-aligned .wy-control-group label { + margin-bottom: 0.3em; + text-align: left; + display: block; + width: 100%; + } + + .wy-form-aligned .wy-controls { + margin: 1.5em 0 0 0; + } + + .wy-form .wy-help-inline, .wy-form-message-inline, .wy-form-message { + display: block; + font-size: 80%; + padding: 0.2em 0 0.8em; + } +} + +@media screen and (max-width: 768px) { + .tablet-hide { + display: none; + } +} + +@media screen and (max-width: 480px) { + .mobile-hide { + display: none; + } +} + +.float-left { + float: left; +} + +.float-right { + float: right; +} + +.full-width { + width: 100%; +} + +.wy-grid-one-col { + *zoom: 1; + max-width: 68em; + margin-left: auto; + margin-right: auto; + max-width: 1066px; + margin-top: 1.618em; +} + +.wy-grid-one-col:before, .wy-grid-one-col:after { + display: table; + content: ""; +} + +.wy-grid-one-col:after { + clear: both; +} + +.wy-grid-one-col section { + display: block; + float: left; + margin-right: 2.35765%; + width: 100%; + background: #fff; + padding: 1.618em; + margin-right: 0; +} + +.wy-grid-one-col section:last-child { + margin-right: 0; +} + +.wy-grid-index-card { + *zoom: 1; + max-width: 68em; + margin-left: auto; + margin-right: auto; + max-width: 460px; + margin-top: 1.618em; + background: #fff; + padding: 1.618em; +} + +.wy-grid-index-card:before, .wy-grid-index-card:after { + display: table; + content: ""; +} + +.wy-grid-index-card:after { + clear: both; +} + +.wy-grid-index-card header, .wy-grid-index-card section, .wy-grid-index-card aside { + display: block; + float: left; + margin-right: 2.35765%; + width: 100%; +} + +.wy-grid-index-card header:last-child, .wy-grid-index-card section:last-child, .wy-grid-index-card aside:last-child { + margin-right: 0; +} + +.wy-grid-index-card.twocol { + max-width: 768px; +} + +.wy-grid-index-card.twocol section { + display: block; + float: left; + margin-right: 2.35765%; + width: 48.82117%; +} + +.wy-grid-index-card.twocol section:last-child { + margin-right: 0; +} + +.wy-grid-index-card.twocol aside { + display: block; + float: left; + margin-right: 2.35765%; + width: 48.82117%; +} + +.wy-grid-index-card.twocol aside:last-child { + margin-right: 0; +} + +.wy-grid-search-filter { + *zoom: 1; + max-width: 68em; + margin-left: auto; + margin-right: auto; + margin-bottom: 24px; +} + +.wy-grid-search-filter:before, .wy-grid-search-filter:after { + display: table; + content: ""; +} + +.wy-grid-search-filter:after { + clear: both; +} + +.wy-grid-search-filter .wy-grid-search-filter-input { + display: block; + float: left; + margin-right: 2.35765%; + width: 74.41059%; +} + +.wy-grid-search-filter .wy-grid-search-filter-input:last-child { + margin-right: 0; +} + +.wy-grid-search-filter .wy-grid-search-filter-btn { + display: block; + float: left; + margin-right: 2.35765%; + width: 23.23176%; +} + +.wy-grid-search-filter .wy-grid-search-filter-btn:last-child { + margin-right: 0; +} + +.wy-table, .rst-content table.docutils, .rst-content table.field-list { + border-collapse: collapse; + border-spacing: 0; + empty-cells: show; + margin-bottom: 24px; +} + +.wy-table caption, .rst-content table.docutils caption, .rst-content table.field-list caption { + color: #000; + font: italic 85%/1 arial, sans-serif; + padding: 1em 0; + text-align: center; +} + +.wy-table td, .rst-content table.docutils td, .rst-content table.field-list td, .wy-table th, .rst-content table.docutils th, .rst-content table.field-list th { + font-size: 90%; + margin: 0; + overflow: visible; + padding: 8px 16px; +} + +.wy-table td:first-child, .rst-content table.docutils td:first-child, .rst-content table.field-list td:first-child, .wy-table th:first-child, .rst-content table.docutils th:first-child, .rst-content table.field-list th:first-child { + border-left-width: 0; +} + +.wy-table thead, .rst-content table.docutils thead, .rst-content table.field-list thead { + color: #000; + text-align: left; + vertical-align: bottom; + white-space: nowrap; +} + +.wy-table thead th, .rst-content table.docutils thead th, .rst-content table.field-list thead th { + font-weight: bold; + border-bottom: solid 2px #e1e4e5; +} + +.wy-table td, .rst-content table.docutils td, .rst-content table.field-list td { + background-color: transparent; + vertical-align: middle; +} + +.wy-table td p, .rst-content table.docutils td p, .rst-content table.field-list td p { + line-height: 18px; + margin-bottom: 0; +} + +.wy-table .wy-table-cell-min, .rst-content table.docutils .wy-table-cell-min, .rst-content table.field-list .wy-table-cell-min { + width: 1%; + padding-right: 0; +} + +.wy-table .wy-table-cell-min input[type=checkbox], .rst-content table.docutils .wy-table-cell-min input[type=checkbox], .rst-content table.field-list .wy-table-cell-min input[type=checkbox], .wy-table .wy-table-cell-min input[type=checkbox], .rst-content table.docutils .wy-table-cell-min input[type=checkbox], .rst-content table.field-list .wy-table-cell-min input[type=checkbox] { + margin: 0; +} + +.wy-table-secondary { + color: gray; + font-size: 90%; +} + +.wy-table-tertiary { + color: gray; + font-size: 80%; +} + +.wy-table-odd td, .wy-table-striped tr:nth-child(2n-1) td, .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { + background-color: #f3f6f6; +} + +.wy-table-backed { + background-color: #f3f6f6; +} + +.wy-table-bordered-all, .rst-content table.docutils { + border: 1px solid #e1e4e5; +} + +.wy-table-bordered-all td, .rst-content table.docutils td { + border-bottom: 1px solid #e1e4e5; + border-left: 1px solid #e1e4e5; +} + +.wy-table-bordered-all tbody>tr:last-child td, .rst-content table.docutils tbody>tr:last-child td { + border-bottom-width: 0; +} + +.wy-table-bordered { + border: 1px solid #e1e4e5; +} + +.wy-table-bordered-rows td { + border-bottom: 1px solid #e1e4e5; +} + +.wy-table-bordered-rows tbody>tr:last-child td { + border-bottom-width: 0; +} + +.wy-table-horizontal tbody>tr:last-child td { + border-bottom-width: 0; +} + +.wy-table-horizontal td, .wy-table-horizontal th { + border-width: 0 0 1px 0; + border-bottom: 1px solid #e1e4e5; +} + +.wy-table-horizontal tbody>tr:last-child td { + border-bottom-width: 0; +} + +.wy-table-responsive { + margin-bottom: 24px; + max-width: 100%; + overflow: auto; +} + +.wy-table-responsive table { + margin-bottom: 0 !important; +} + +.wy-table-responsive table td, .wy-table-responsive table th { + white-space: nowrap; +} + +html { + height: 100%; + overflow-x: hidden; +} + +body { + font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; + font-weight: normal; + color: #404040; + min-height: 100%; + overflow-x: hidden; + background: #edf0f2; +} + +a { + color: #2980b9; + text-decoration: none; +} + +a:hover { + color: #3091d1; +} + +.link-danger { + color: #e74c3c; +} + +.link-danger:hover { + color: #d62c1a; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +h1, h2, h3, h4, h5, h6, legend { + margin-top: 0; + font-weight: 700; + font-family: "Roboto Slab", "ff-tisa-web-pro", "Georgia", Arial, sans-serif; +} + +p { + line-height: 24px; + margin: 0; + font-size: 16px; + margin-bottom: 24px; +} + +h1 { + font-size: 175%; +} + +h2 { + font-size: 150%; +} + +h3 { + font-size: 125%; +} + +h4 { + font-size: 115%; +} + +h5 { + font-size: 110%; +} + +h6 { + font-size: 100%; +} + +small { + font-size: 80%; +} + +code, .rst-content tt { + white-space: nowrap; + max-width: 100%; + background: #fff; + border: solid 1px #e1e4e5; + font-size: 75%; + padding: 0 5px; + font-family: "Incosolata", "Consolata", "Monaco", monospace; + color: #e74c3c; + overflow-x: auto; +} + +code.code-large, .rst-content tt.code-large { + font-size: 90%; +} + +.full-width { + width: 100%; +} + +.wy-plain-list-disc, .rst-content .section ul, .rst-content .toctree-wrapper ul { + list-style: disc; + line-height: 24px; + margin-bottom: 24px; +} + +.wy-plain-list-disc li, .rst-content .section ul li, .rst-content .toctree-wrapper ul li { + list-style: disc; + margin-left: 24px; +} + +.wy-plain-list-disc li ul, .rst-content .section ul li ul, .rst-content .toctree-wrapper ul li ul { + margin-bottom: 0; +} + +.wy-plain-list-disc li li, .rst-content .section ul li li, .rst-content .toctree-wrapper ul li li { + list-style: circle; +} + +.wy-plain-list-disc li li li, .rst-content .section ul li li li, .rst-content .toctree-wrapper ul li li li { + list-style: square; +} + +.wy-plain-list-decimal, .rst-content .section ol, .rst-content ol.arabic { + list-style: decimal; + line-height: 24px; + margin-bottom: 24px; +} + +.wy-plain-list-decimal li, .rst-content .section ol li, .rst-content ol.arabic li { + list-style: decimal; + margin-left: 24px; +} + +.wy-type-large { + font-size: 120%; +} + +.wy-type-normal { + font-size: 100%; +} + +.wy-type-small { + font-size: 100%; +} + +.wy-type-strike { + text-decoration: line-through; +} + +.wy-text-warning { + color: #e67e22 !important; +} + +a.wy-text-warning:hover { + color: #eb9950 !important; +} + +.wy-text-info { + color: #2980b9 !important; +} + +a.wy-text-info:hover { + color: #409ad5 !important; +} + +.wy-text-success { + color: #27ae60 !important; +} + +a.wy-text-success:hover { + color: #36d278 !important; +} + +.wy-text-danger { + color: #e74c3c !important; +} + +a.wy-text-danger:hover { + color: #ed7669 !important; +} + +.wy-text-neutral { + color: #404040 !important; +} + +a.wy-text-neutral:hover { + color: #595959 !important; +} + +.codeblock-example { + border: 1px solid #e1e4e5; + border-bottom: none; + padding: 24px; + padding-top: 48px; + font-weight: 500; + background: #fff; + position: relative; +} + +.codeblock-example:after { + content: "Example"; + position: absolute; + top: 0px; + left: 0px; + background: #9b59b6; + color: #fff; + padding: 6px 12px; +} + +.codeblock-example.prettyprint-example-only { + border: 1px solid #e1e4e5; + margin-bottom: 24px; +} + +.codeblock, div[class^='highlight'] { + border: 1px solid #e1e4e5; + padding: 0px; + overflow-x: auto; + background: #fff; + margin: 1px 0 24px 0; +} + +.codeblock div[class^='highlight'], div[class^='highlight'] div[class^='highlight'] { + border: none; + background: none; + margin: 0; +} + +.linenodiv pre { + border-right: solid 1px #e6e9ea; + margin: 0; + padding: 12px 12px; + font-family: "Incosolata", "Consolata", "Monaco", monospace; + font-size: 12px; + line-height: 1.5; + color: #d9d9d9; +} + +div[class^='highlight'] pre { + white-space: pre; + margin: 0; + padding: 12px 12px; + font-family: "Incosolata", "Consolata", "Monaco", monospace; + font-size: 12px; + line-height: 1.5; + display: block; + overflow: auto; + color: #404040; +} + +pre.literal-block { + @extends .codeblock; +} + +@media print { + .codeblock, div[class^='highlight'], div[class^='highlight'] pre { + white-space: pre-wrap; + } +} + +.hll { + background-color: #f8f8f8; + border: 1px solid #ccc; + padding: 1.5px 5px; +} + +.c { + color: #998; + font-style: italic; +} + +.err { + color: #a61717; + background-color: #e3d2d2; +} + +.k { + font-weight: bold; +} + +.o { + font-weight: bold; +} + +.cm { + color: #998; + font-style: italic; +} + +.cp { + color: #999; + font-weight: bold; +} + +.c1 { + color: #998; + font-style: italic; +} + +.cs { + color: #999; + font-weight: bold; + font-style: italic; +} + +.gd { + color: #000; + background-color: #fdd; +} + +.gd .x { + color: #000; + background-color: #faa; +} + +.ge { + font-style: italic; +} + +.gr { + color: #a00; +} + +.gh { + color: #999; +} + +.gi { + color: #000; + background-color: #dfd; +} + +.gi .x { + color: #000; + background-color: #afa; +} + +.go { + color: #888; +} + +.gp { + color: #555; +} + +.gs { + font-weight: bold; +} + +.gu { + color: purple; + font-weight: bold; +} + +.gt { + color: #a00; +} + +.kc { + font-weight: bold; +} + +.kd { + font-weight: bold; +} + +.kn { + font-weight: bold; +} + +.kp { + font-weight: bold; +} + +.kr { + font-weight: bold; +} + +.kt { + color: #458; + font-weight: bold; +} + +.m { + color: #099; +} + +.s { + color: #d14; +} + +.n { + color: #333; +} + +.na { + color: teal; +} + +.nb { + color: #0086b3; +} + +.nc { + color: #458; + font-weight: bold; +} + +.no { + color: teal; +} + +.ni { + color: purple; +} + +.ne { + color: #900; + font-weight: bold; +} + +.nf { + color: #900; + font-weight: bold; +} + +.nn { + color: #555; +} + +.nt { + color: navy; +} + +.nv { + color: teal; +} + +.ow { + font-weight: bold; +} + +.w { + color: #bbb; +} + +.mf { + color: #099; +} + +.mh { + color: #099; +} + +.mi { + color: #099; +} + +.mo { + color: #099; +} + +.sb { + color: #d14; +} + +.sc { + color: #d14; +} + +.sd { + color: #d14; +} + +.s2 { + color: #d14; +} + +.se { + color: #d14; +} + +.sh { + color: #d14; +} + +.si { + color: #d14; +} + +.sx { + color: #d14; +} + +.sr { + color: #009926; +} + +.s1 { + color: #d14; +} + +.ss { + color: #990073; +} + +.bp { + color: #999; +} + +.vc { + color: teal; +} + +.vg { + color: teal; +} + +.vi { + color: teal; +} + +.il { + color: #099; +} + +.gc { + color: #999; + background-color: #eaf2f5; +} + +.wy-breadcrumbs li { + display: inline-block; +} + +.wy-breadcrumbs li.wy-breadcrumbs-aside { + float: right; +} + +.wy-breadcrumbs li a { + display: inline-block; + padding: 5px; +} + +.wy-breadcrumbs li a:first-child { + padding-left: 0; +} + +.wy-breadcrumbs-extra { + margin-bottom: 0; + color: #b3b3b3; + font-size: 80%; + display: inline-block; +} + +@media screen and (max-width: 480px) { + .wy-breadcrumbs-extra { + display: none; + } + + .wy-breadcrumbs li.wy-breadcrumbs-aside { + display: none; + } +} + +@media print { + .wy-breadcrumbs li.wy-breadcrumbs-aside { + display: none; + } +} + +.wy-affix { + position: fixed; + top: 1.618em; +} + +.wy-menu a:hover { + text-decoration: none; +} + +.wy-menu-horiz { + *zoom: 1; +} + +.wy-menu-horiz:before, .wy-menu-horiz:after { + display: table; + content: ""; +} + +.wy-menu-horiz:after { + clear: both; +} + +.wy-menu-horiz ul, .wy-menu-horiz li { + display: inline-block; +} + +.wy-menu-horiz li:hover { + background: rgba(255, 255, 255, 0.1); +} + +.wy-menu-horiz li.divide-left { + border-left: solid 1px #404040; +} + +.wy-menu-horiz li.divide-right { + border-right: solid 1px #404040; +} + +.wy-menu-horiz a { + height: 32px; + display: inline-block; + line-height: 32px; + padding: 0 16px; +} + +.wy-menu-vertical header { + height: 32px; + display: inline-block; + line-height: 32px; + padding: 0 1.618em; + display: block; + font-weight: bold; + text-transform: uppercase; + font-size: 80%; + color: #2980b9; + white-space: nowrap; +} + +.wy-menu-vertical ul { + margin-bottom: 0; +} + +.wy-menu-vertical li.divide-top { + border-top: solid 1px #404040; +} + +.wy-menu-vertical li.divide-bottom { + border-bottom: solid 1px #404040; +} + +.wy-menu-vertical li.current { + background: #e3e3e3; +} + +.wy-menu-vertical li.current a { + color: gray; + border-right: solid 1px #c9c9c9; + padding: 0.4045em 2.427em; +} + +.wy-menu-vertical li.current a:hover { + background: #d6d6d6; +} + +.wy-menu-vertical li.on a, .wy-menu-vertical li.current>a { + color: #404040; + padding: 0.4045em 1.618em; + font-weight: bold; + position: relative; + background: #fcfcfc; + border: none; + border-bottom: solid 1px #c9c9c9; + border-top: solid 1px #c9c9c9; + padding-left: 1.618em -4px; +} + +.wy-menu-vertical li.on a:hover, .wy-menu-vertical li.current>a:hover { + background: #fcfcfc; +} + +.wy-menu-vertical li.tocktree-l2.current>a { + background: #c9c9c9; +} + +.wy-menu-vertical li.current ul { + display: block; +} + +.wy-menu-vertical li ul { + margin-bottom: 0; + display: none; +} + +.wy-menu-vertical li ul li a { + margin-bottom: 0; + color: #b3b3b3; + font-weight: normal; +} + +.wy-menu-vertical a { + display: inline-block; + line-height: 18px; + padding: 0.4045em 1.618em; + display: block; + position: relative; + font-size: 90%; + color: #b3b3b3; +} + +.wy-menu-vertical a:hover { + background-color: #4e4a4a; + cursor: pointer; +} + +.wy-menu-vertical a:active { + background-color: #2980b9; + cursor: pointer; + color: #fff; +} + +.wy-side-nav-search { + z-index: 200; + background-color: #2980b9; + text-align: center; + padding: 0.809em; + display: block; + color: #fcfcfc; + margin-bottom: 0.809em; +} + +.wy-side-nav-search input[type=text] { + width: 100%; + border-radius: 50px; + padding: 6px 12px; + border-color: #2472a4; +} + +.wy-side-nav-search img { + display: block; + margin: auto auto 0.809em auto; + height: 45px; + width: 45px; + background-color: #2980b9; + padding: 5px; + border-radius: 100%; +} + +.wy-side-nav-search>a, .wy-side-nav-search .wy-dropdown>a { + color: #fcfcfc; + font-size: 100%; + font-weight: bold; + display: inline-block; + padding: 4px 6px; + margin-bottom: 0.809em; +} + +.wy-side-nav-search>a:hover, .wy-side-nav-search .wy-dropdown>a:hover { + background: rgba(255, 255, 255, 0.1); +} + +.wy-nav .wy-menu-vertical header { + color: #2980b9; +} + +.wy-nav .wy-menu-vertical a { + color: #b3b3b3; +} + +.wy-nav .wy-menu-vertical a:hover { + background-color: #2980b9; + color: #fff; +} + +[data-menu-wrap] { + -webkit-transition: all 0.2s ease-in; + -moz-transition: all 0.2s ease-in; + transition: all 0.2s ease-in; + position: absolute; + opacity: 1; + width: 100%; + opacity: 0; +} + +[data-menu-wrap].move-center { + left: 0; + right: auto; + opacity: 1; +} + +[data-menu-wrap].move-left { + right: auto; + left: -100%; + opacity: 0; +} + +[data-menu-wrap].move-right { + right: -100%; + left: auto; + opacity: 0; +} + +.wy-body-for-nav { + background: left repeat-y #fff; + background-image: url(data:image/png; + base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC); + background-size: 300px 1px; +} + +.wy-grid-for-nav { + position: absolute; + width: 100%; + height: 100%; +} + +.wy-nav-side { + position: absolute; + top: 0; + left: 0; + width: 300px; + overflow: hidden; + min-height: 100%; + background: #343131; + z-index: 200; +} + +.wy-nav-top { + display: none; + background: #2980b9; + color: #fff; + padding: 0.4045em 0.809em; + position: relative; + line-height: 50px; + text-align: center; + font-size: 100%; + *zoom: 1; +} + +.wy-nav-top:before, .wy-nav-top:after { + display: table; + content: ""; +} + +.wy-nav-top:after { + clear: both; +} + +.wy-nav-top a { + color: #fff; + font-weight: bold; +} + +.wy-nav-top img { + margin-right: 12px; + height: 45px; + width: 45px; + background-color: #2980b9; + padding: 5px; + border-radius: 100%; +} + +.wy-nav-top i { + font-size: 30px; + float: left; + cursor: pointer; +} + +.wy-nav-content-wrap { + margin-left: 300px; + background: #fff; + min-height: 100%; +} + +.wy-nav-content { + padding: 1.618em 3.236em; + height: 100%; + max-width: 1140px; + margin: auto; +} + +.wy-body-mask { + position: fixed; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.2); + display: none; + z-index: 499; +} + +.wy-body-mask.on { + display: block; +} + +footer { + color: #999; +} + +footer p { + margin-bottom: 12px; +} + +.rst-footer-buttons { + *zoom: 1; +} + +.rst-footer-buttons:before, .rst-footer-buttons:after { + display: table; + content: ""; +} + +.rst-footer-buttons:after { + clear: both; +} + +#search-results .search li { + margin-bottom: 24px; + border-bottom: solid 1px #e1e4e5; + padding-bottom: 24px; +} + +#search-results .search li:first-child { + border-top: solid 1px #e1e4e5; + padding-top: 24px; +} + +#search-results .search li a { + font-size: 120%; + margin-bottom: 12px; + display: inline-block; +} + +#search-results .context { + color: gray; + font-size: 90%; +} + +@media screen and (max-width: 768px) { + .wy-body-for-nav { + background: #fff; + } + + .wy-nav-top { + display: block; + } + + .wy-nav-side { + left: -300px; + } + + .wy-nav-side.shift { + width: 85%; + left: 0; + } + + .wy-nav-content-wrap { + margin-left: 0; + } + + .wy-nav-content-wrap .wy-nav-content { + padding: 1.618em; + } + + .wy-nav-content-wrap.shift { + position: fixed; + min-width: 100%; + left: 85%; + top: 0; + height: 100%; + overflow: hidden; + } +} + +@media screen and (min-width: 1400px) { + .wy-nav-content-wrap { + background: #fff; + } + + .wy-nav-content { + margin: 0; + background: #fff; + } +} + +@media print { + .wy-nav-side { + display: none; + } + + .wy-nav-content-wrap { + margin-left: 0; + } +} + +.rst-versions { + position: fixed; + bottom: 0; + left: 0; + width: 300px; + color: #fcfcfc; + background: #1f1d1d; + border-top: solid 10px #343131; + font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; + z-index: 400; +} + +.rst-versions a { + color: #2980b9; + text-decoration: none; +} + +.rst-versions .rst-badge-small { + display: none; +} + +.rst-versions .rst-current-version { + padding: 12px; + background-color: #272525; + display: block; + text-align: right; + font-size: 90%; + cursor: pointer; + color: #27ae60; + *zoom: 1; +} + +.rst-versions .rst-current-version:before, .rst-versions .rst-current-version:after { + display: table; + content: ""; +} + +.rst-versions .rst-current-version:after { + clear: both; +} + +.rst-versions .rst-current-version .icon, .rst-versions .rst-current-version .wy-inline-validate.wy-inline-validate-success .wy-input-context, .wy-inline-validate.wy-inline-validate-success .rst-versions .rst-current-version .wy-input-context, .rst-versions .rst-current-version .wy-inline-validate.wy-inline-validate-danger .wy-input-context, .wy-inline-validate.wy-inline-validate-danger .rst-versions .rst-current-version .wy-input-context, .rst-versions .rst-current-version .wy-inline-validate.wy-inline-validate-warning .wy-input-context, .wy-inline-validate.wy-inline-validate-warning .rst-versions .rst-current-version .wy-input-context, .rst-versions .rst-current-version .wy-inline-validate.wy-inline-validate-info .wy-input-context, .wy-inline-validate.wy-inline-validate-info .rst-versions .rst-current-version .wy-input-context, .rst-versions .rst-current-version .wy-tag-input-group .wy-tag .wy-tag-remove, .wy-tag-input-group .wy-tag .rst-versions .rst-current-version .wy-tag-remove, .rst-versions .rst-current-version .rst-content .admonition-title, .rst-content .rst-versions .rst-current-version .admonition-title, .rst-versions .rst-current-version .rst-content h1 .headerlink, .rst-content h1 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h2 .headerlink, .rst-content h2 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h3 .headerlink, .rst-content h3 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h4 .headerlink, .rst-content h4 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h5 .headerlink, .rst-content h5 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h6 .headerlink, .rst-content h6 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content dl dt .headerlink, .rst-content dl dt .rst-versions .rst-current-version .headerlink { + color: #fcfcfc; +} + +.rst-versions .rst-current-version .icon-book { + float: left; +} + +.rst-versions .rst-current-version.rst-out-of-date { + background-color: #e74c3c; + color: #fff; +} + +.rst-versions.shift-up .rst-other-versions { + display: block; +} + +.rst-versions .rst-other-versions { + font-size: 90%; + padding: 12px; + color: gray; + display: none; +} + +.rst-versions .rst-other-versions hr { + display: block; + height: 1px; + border: 0; + margin: 20px 0; + padding: 0; + border-top: solid 1px #413d3d; +} + +.rst-versions .rst-other-versions dd { + display: inline-block; + margin: 0; +} + +.rst-versions .rst-other-versions dd a { + display: inline-block; + padding: 6px; + color: #fcfcfc; +} + +.rst-versions.rst-badge { + width: auto; + bottom: 20px; + right: 20px; + left: auto; + border: none; + max-width: 300px; +} + +.rst-versions.rst-badge .icon-book { + float: none; +} + +.rst-versions.rst-badge.shift-up .rst-current-version { + text-align: right; +} + +.rst-versions.rst-badge.shift-up .rst-current-version .icon-book { + float: left; +} + +.rst-versions.rst-badge .rst-current-version { + width: auto; + height: 30px; + line-height: 30px; + padding: 0 6px; + display: block; + text-align: center; +} + +@media screen and (max-width: 768px) { + .rst-versions { + width: 85%; + display: none; + } + + .rst-versions.shift { + display: block; + } + + img { + width: 100%; + height: auto; + } +} + +.rst-content img { + max-width: 100%; + height: auto !important; +} + +.rst-content .section>img { + margin-bottom: 24px; +} + +.rst-content a.reference.external:after { + font-family: fontawesome-webfont; + content: " \f08e "; + color: #b3b3b3; + vertical-align: super; + font-size: 60%; +} + +.rst-content blockquote { + margin-left: 24px; + line-height: 24px; + margin-bottom: 24px; +} + +.rst-content .note .last, .rst-content .note p.first, .rst-content .attention .last, .rst-content .attention p.first, .rst-content .caution .last, .rst-content .caution p.first, .rst-content .danger .last, .rst-content .danger p.first, .rst-content .error .last, .rst-content .error p.first, .rst-content .hint .last, .rst-content .hint p.first, .rst-content .important .last, .rst-content .important p.first, .rst-content .tip .last, .rst-content .tip p.first, .rst-content .warning .last, .rst-content .warning p.first { + margin-bottom: 0; +} + +.rst-content .admonition-title { + font-weight: bold; +} + +.rst-content .admonition-title:before { + margin-right: 4px; +} + +.rst-content .admonition table { + border-color: rgba(0, 0, 0, 0.1); +} + +.rst-content .admonition table td, .rst-content .admonition table th { + background: transparent !important; + border-color: rgba(0, 0, 0, 0.1) !important; +} + +.rst-content .section ol.loweralpha, .rst-content .section ol.loweralpha li { + list-style: lower-alpha; +} + +.rst-content .section ol.upperalpha, .rst-content .section ol.upperalpha li { + list-style: upper-alpha; +} + +.rst-content .section ol p, .rst-content .section ul p { + margin-bottom: 12px; +} + +.rst-content .line-block { + margin-left: 24px; +} + +.rst-content .topic-title { + font-weight: bold; + margin-bottom: 12px; +} + +.rst-content .toc-backref { + color: #404040; +} + +.rst-content .align-right { + float: right; + margin: 0px 0px 24px 24px; +} + +.rst-content .align-left { + float: left; + margin: 0px 24px 24px 0px; +} + +.rst-content h1 .headerlink, .rst-content h2 .headerlink, .rst-content h3 .headerlink, .rst-content h4 .headerlink, .rst-content h5 .headerlink, .rst-content h6 .headerlink, .rst-content dl dt .headerlink { + display: none; + visibility: hidden; + font-size: 14px; +} + +.rst-content h1 .headerlink:after, .rst-content h2 .headerlink:after, .rst-content h3 .headerlink:after, .rst-content h4 .headerlink:after, .rst-content h5 .headerlink:after, .rst-content h6 .headerlink:after, .rst-content dl dt .headerlink:after { + visibility: visible; + content: "\f0c1"; + font-family: fontawesome-webfont; + display: inline-block; +} + +.rst-content h1:hover .headerlink, .rst-content h2:hover .headerlink, .rst-content h3:hover .headerlink, .rst-content h4:hover .headerlink, .rst-content h5:hover .headerlink, .rst-content h6:hover .headerlink, .rst-content dl dt:hover .headerlink { + display: inline-block; +} + +.rst-content .sidebar { + float: right; + width: 40%; + display: block; + margin: 0 0 24px 24px; + padding: 24px; + background: #f3f6f6; + border: solid 1px #e1e4e5; +} + +.rst-content .sidebar p, .rst-content .sidebar ul, .rst-content .sidebar dl { + font-size: 90%; +} + +.rst-content .sidebar .last { + margin-bottom: 0; +} + +.rst-content .sidebar .sidebar-title { + display: block; + font-family: "Roboto Slab", "ff-tisa-web-pro", "Georgia", Arial, sans-serif; + font-weight: bold; + background: #e1e4e5; + padding: 6px 12px; + margin: -24px; + margin-bottom: 24px; + font-size: 100%; +} + +.rst-content .highlighted { + background: #f1c40f; + display: inline-block; + font-weight: bold; + padding: 0 6px; +} + +.rst-content .footnote-reference, .rst-content .citation-reference { + vertical-align: super; + font-size: 90%; +} + +.rst-content table.docutils.citation, .rst-content table.docutils.footnote { + background: none; + border: none; + color: #999; +} + +.rst-content table.docutils.citation td, .rst-content table.docutils.citation tr, .rst-content table.docutils.footnote td, .rst-content table.docutils.footnote tr { + border: none; + background-color: transparent !important; + white-space: normal; +} + +.rst-content table.docutils.citation td.label, .rst-content table.docutils.footnote td.label { + padding-left: 0; + padding-right: 0; + vertical-align: top; +} + +.rst-content table.field-list { + border: none; +} + +.rst-content table.field-list td { + border: none; +} + +.rst-content table.field-list .field-name { + padding-right: 10px; + text-align: left; +} + +.rst-content table.field-list .field-body { + text-align: left; + padding-left: 0; +} + +.rst-content tt { + color: #000; +} + +.rst-content tt big, .rst-content tt em { + font-size: 100% !important; + line-height: normal; +} + +.rst-content tt .xref, a .rst-content tt { + font-weight: bold; +} + +.rst-content dl { + margin-bottom: 24px; +} + +.rst-content dl dt { + font-weight: bold; +} + +.rst-content dl p, .rst-content dl table, .rst-content dl ul, .rst-content dl ol { + margin-bottom: 12px !important; +} + +.rst-content dl dd { + margin: 0 0 12px 24px; +} + +.rst-content dl:not(.docutils) { + margin-bottom: 24px; +} + +.rst-content dl:not(.docutils) dt { + display: inline-block; + margin: 6px 0; + font-size: 90%; + line-height: normal; + background: #e7f2fa; + color: #2980b9; + border-top: solid 3px #6ab0de; + padding: 6px; + position: relative; +} + +.rst-content dl:not(.docutils) dt:before { + color: #6ab0de; +} + +.rst-content dl:not(.docutils) dt .headerlink { + color: #404040; + font-size: 100% !important; +} + +.rst-content dl:not(.docutils) dl dt { + margin-bottom: 6px; + border: none; + border-left: solid 3px #ccc; + background: #f0f0f0; + color: gray; +} + +.rst-content dl:not(.docutils) dl dt .headerlink { + color: #404040; + font-size: 100% !important; +} + +.rst-content dl:not(.docutils) dt:first-child { + margin-top: 0; +} + +.rst-content dl:not(.docutils) tt { + font-weight: bold; +} + +.rst-content dl:not(.docutils) tt.descname, .rst-content dl:not(.docutils) tt.descclassname { + background-color: transparent; + border: none; + padding: 0; + font-size: 100% !important; +} + +.rst-content dl:not(.docutils) tt.descname { + font-weight: bold; +} + +.rst-content dl:not(.docutils) .viewcode-link { + display: inline-block; + color: #27ae60; + font-size: 80%; + padding-left: 24px; +} + +.rst-content dl:not(.docutils) .optional { + display: inline-block; + padding: 0 4px; + color: #000; + font-weight: bold; +} + +.rst-content dl:not(.docutils) .property { + display: inline-block; + padding-right: 8px; +} + +@media screen and (max-width: 480px) { + .rst-content .sidebar { + width: 100%; + } +} + +span[id*='MathJax-Span'] { + color: #404040; +} + +.admonition.note span[id*='MathJax-Span'] { + color: #fff; +} + +.admonition.warning span[id*='MathJax-Span'] { + color: #fff; +} + +.search-reset-start { + color: #463E3F; + float: right; + position: relative; + top: -25px; + left: -10px; + z-index: 10; +} + +.search-reset-start:hover { + cursor: pointer; + color: #2980B9; +} + +#search-box-id { + padding-right: 25px; +} diff --git a/docsite/_themes/srtd/static/font/fontawesome_webfont.eot b/docsite/_themes/srtd/static/font/fontawesome_webfont.eot new file mode 100644 index 0000000000..0662cb96bf Binary files /dev/null and b/docsite/_themes/srtd/static/font/fontawesome_webfont.eot differ diff --git a/docsite/_themes/srtd/static/font/fontawesome_webfont.svg b/docsite/_themes/srtd/static/font/fontawesome_webfont.svg new file mode 100644 index 0000000000..2edb4ec34c --- /dev/null +++ b/docsite/_themes/srtd/static/font/fontawesome_webfont.svg @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docsite/_themes/srtd/static/font/fontawesome_webfont.ttf b/docsite/_themes/srtd/static/font/fontawesome_webfont.ttf new file mode 100644 index 0000000000..d365924691 Binary files /dev/null and b/docsite/_themes/srtd/static/font/fontawesome_webfont.ttf differ diff --git a/docsite/_themes/srtd/static/font/fontawesome_webfont.woff b/docsite/_themes/srtd/static/font/fontawesome_webfont.woff new file mode 100644 index 0000000000..b9bd17e158 Binary files /dev/null and b/docsite/_themes/srtd/static/font/fontawesome_webfont.woff differ diff --git a/docsite/_themes/srtd/static/js/theme.js b/docsite/_themes/srtd/static/js/theme.js new file mode 100644 index 0000000000..58e514c0c3 --- /dev/null +++ b/docsite/_themes/srtd/static/js/theme.js @@ -0,0 +1,16 @@ +$( document ).ready(function() { + // Shift nav in mobile when clicking the menu. + $("[data-toggle='wy-nav-top']").click(function() { + $("[data-toggle='wy-nav-shift']").toggleClass("shift"); + $("[data-toggle='rst-versions']").toggleClass("shift"); + }); + // Close menu when you click a link. + $(".wy-menu-vertical .current ul li a").click(function() { + $("[data-toggle='wy-nav-shift']").removeClass("shift"); + $("[data-toggle='rst-versions']").toggleClass("shift"); + }); + $("[data-toggle='rst-current-version']").click(function() { + $("[data-toggle='rst-versions']").toggleClass("shift-up"); + }); + $("table.docutils:not(.field-list").wrap("
    "); +}); diff --git a/docsite/_themes/srtd/theme.conf b/docsite/_themes/srtd/theme.conf new file mode 100644 index 0000000000..ba94c7f32e --- /dev/null +++ b/docsite/_themes/srtd/theme.conf @@ -0,0 +1,7 @@ +[theme] +inherit = basic +stylesheet = css/theme.min.css + +[options] +typekit_id = hiw1hhg +analytics_id = diff --git a/docsite/_themes/srtd/versions.html b/docsite/_themes/srtd/versions.html new file mode 100644 index 0000000000..93319be892 --- /dev/null +++ b/docsite/_themes/srtd/versions.html @@ -0,0 +1,37 @@ +{% if READTHEDOCS %} +{# Add rst-badge after rst-versions for small badge style. #} +
    + + Read the Docs + v: {{ current_version }} + + +
    +
    +
    Versions
    + {% for slug, url in versions %} +
    {{ slug }}
    + {% endfor %} +
    +
    +
    Downloads
    + {% for type, url in downloads %} +
    {{ type }}
    + {% endfor %} +
    +
    +
    On Read the Docs
    +
    + Project Home +
    +
    + Builds +
    +
    +
    + Free document hosting provided by Read the Docs. + +
    +
    +{% endif %} + diff --git a/docsite/conf.py b/docsite/conf.py index 6c6aff2d65..fae3a156f2 100644 --- a/docsite/conf.py +++ b/docsite/conf.py @@ -16,11 +16,15 @@ import sys import os +# pip install sphinx_rtd_theme +#import sphinx_rtd_theme +#html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. #sys.path.append(os.path.abspath('some/directory')) - +# sys.path.insert(0, os.path.join('ansible', 'lib')) sys.path.append(os.path.abspath('_themes')) @@ -50,7 +54,7 @@ source_suffix = '.rst' master_doc = 'index' # General substitutions. -project = 'Ansible 1.2 Documentation' +project = 'Ansible Documentation' copyright = "2013 AnsibleWorks" # The default replacements for |version| and |release|, also used in various @@ -101,17 +105,17 @@ pygments_style = 'sphinx' # ----------------------- html_theme_path = ['_themes'] -html_theme = 'aworks' +html_theme = 'srtd' html_short_title = 'Ansible Documentation' # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. -html_style = 'default.css' +#html_style = 'solar.css' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -html_title = 'Ansible 1.2 Documentation' +html_title = 'Ansible Documentation' # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None diff --git a/docsite/rst/YAMLSyntax.rst b/docsite/rst/YAMLSyntax.rst index 54874b6dde..3230a39f24 100644 --- a/docsite/rst/YAMLSyntax.rst +++ b/docsite/rst/YAMLSyntax.rst @@ -1,9 +1,6 @@ YAML Syntax =========== -.. contents:: - :depth: 2 - This page provides a basic overview of correct YAML syntax, which is how Ansible playbooks (our configuration management language) are expressed. diff --git a/docsite/rst/awx.rst b/docsite/rst/awx.rst new file mode 100644 index 0000000000..26ebeb8d3b --- /dev/null +++ b/docsite/rst/awx.rst @@ -0,0 +1,11 @@ +AnsibleWorks AWX +```````````````` + +`AnsibleWorks `_, who also sponsors the Ansible community, also produces 'AWX', which is a web-based solution that makes Ansible even more easy to use for IT teams of all kinds. It's designed to be the hub for all of your automation tasks. + +AWX allows you to control access to who can access what, even allowing sharing of SSH credentials without someone being able to transfer those credentials. Inventory can be graphically managed or synced with a wide variety of cloud sources. It logs all of your jobs, integrates well with LDAP, and has an amazing browsable REST API. Command line tools are available for easy integration +with Jenkins as well. + +Find out more about AWX features and how to download it on the `AWX webpage `_. AWX +is free for usage for up to 10 nodes, and comes bundled with amazing support from AnsibleWorks. As you would expect, AWX is +installed using Ansible playbooks! diff --git a/docsite/rst/community.rst b/docsite/rst/community.rst new file mode 100644 index 0000000000..f91408513e --- /dev/null +++ b/docsite/rst/community.rst @@ -0,0 +1,9 @@ +Community Information +````````````````````` + +Ansible is an open source project designed to bring together developers and administrators of all kinds to collaborate on building +IT automation solutions that work well for them. Should you wish to get more involved -- whether in terms of just asking a question, helping other users, introducing new people to Ansible, or helping with the software or documentation, we welcome your contributions to the project. + +`Ways to interact `_ + + diff --git a/docsite/rst/developing.rst b/docsite/rst/developing.rst new file mode 100644 index 0000000000..cc8b03d30f --- /dev/null +++ b/docsite/rst/developing.rst @@ -0,0 +1,16 @@ +Developer Information +````````````````````` + +Learn how to build modules of your own in any language, and also how to extend Ansible through several kinds of plugins. Explore Ansible's Python API and write Python plugins to integrate with other solutions in your environment. + +.. toctree:: + :maxdepth: 1 + + developing_api + developing_inventory + developing_modules + developing_plugins + +Developers will also likely be interested in the fully-discoverable `REST API `_ that is part of AnsibleWorks AWX. It's great for embedding Ansible in all manner of applications. + + diff --git a/docsite/rst/developing_api.rst b/docsite/rst/developing_api.rst index 95042574b2..56cae9d4c2 100644 --- a/docsite/rst/developing_api.rst +++ b/docsite/rst/developing_api.rst @@ -1,6 +1,8 @@ Python API ========== +.. contents:: Topics + There are several interesting ways to use Ansible from an API perspective. You can use the Ansible python API to control nodes, you can extend Ansible to respond to various python events, you can write various plugins, and you can plug in inventory data from external data sources. This document @@ -13,9 +15,6 @@ as it has a very nice REST API that provides all of these things at a higher lev Ansible is written in its own API so you have a considerable amount of power across the board. This chapter discusses the Python API. -.. contents:: `Table of contents` - :depth: 2 - .. _python_api: Python API @@ -38,7 +37,7 @@ It's pretty simple:: The run method returns results per host, grouped by whether they could be contacted or not. Return types are module specific, as -expressed in the 'ansible-modules' documentation.:: +expressed in the :doc:`modules` documentation.:: { "dark" : { diff --git a/docsite/rst/developing_inventory.rst b/docsite/rst/developing_inventory.rst index 4a5af3012c..4630b71114 100644 --- a/docsite/rst/developing_inventory.rst +++ b/docsite/rst/developing_inventory.rst @@ -1,10 +1,10 @@ Developing Dynamic Inventory Sources ==================================== -.. contents:: `Table of contents` - :depth: 2 +.. contents:: Topics + :local: -As described in `intro_inventory_dynamic`, ansible can pull inventory information from dynamic sources, including cloud sources. +As described in :doc:`intro_dynamic_inventory`, ansible can pull inventory information from dynamic sources, including cloud sources. How do we write a new one? diff --git a/docsite/rst/developing_modules.rst b/docsite/rst/developing_modules.rst index 5f9037f61e..ae3f85a4d5 100644 --- a/docsite/rst/developing_modules.rst +++ b/docsite/rst/developing_modules.rst @@ -1,6 +1,8 @@ Developing Modules ================== +.. contents:: Topics + Ansible modules are reusable units of magic that can be used by the Ansible API, or by the `ansible` or `ansible-playbook` programs. @@ -13,9 +15,6 @@ Should you develop an interesting Ansible module, consider sending a pull reques `github project `_ to see about getting your module included in the core project. -.. contents:: - :depth: 2 - .. _module_dev_tutorial: Tutorial @@ -92,7 +91,7 @@ The example usage we are trying to achieve to set the time is:: If no time parameter is set, we'll just leave the time as is and return the current time. -.. note: +.. note:: 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. @@ -229,8 +228,7 @@ The 'group' and 'user' modules are reasonably non-trivial and showcase what this Key parts include always ending the module file with:: - # include magic from lib/ansible/module_common.py - #<> + from ansible.module_utils.basic import * main() And instantiating the module class like:: @@ -375,10 +373,7 @@ syntax highlighting before you include it in your Python file. Example +++++++ -To print a basic documentation string, run ``./hacking/module_formatter.py -G``. - -You can copy it into your module and use it as a starting point -when writing your own docs. +See an example documentation string in the checkout under `examples/DOCUMENTATION.yml `_. Include it in your module file like this:: @@ -392,8 +387,9 @@ Include it in your module file like this:: # ... snip ... ''' -The ``description``, and ``notes`` -support formatting in some of the output formats (e.g. ``rst``, ``man``). +The ``description``, and ``notes`` fields +support formatting with some special macros. + 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 @@ -408,9 +404,8 @@ like this:: - action: modulename opt1=arg1 opt2=arg2 ''' -The ``module_formatter.py`` script and ``ansible-doc(1)`` append the -``EXAMPLES`` blob after any existing (deprecated) ``examples`` you may have in the -YAML ``DOCUMENTATION`` string. +The EXAMPLES section, just like the documentation section, is required in +all module pull requests for new modules. .. _module_dev_testing: diff --git a/docsite/rst/developing_plugins.rst b/docsite/rst/developing_plugins.rst index 2829874d79..041c1c6841 100644 --- a/docsite/rst/developing_plugins.rst +++ b/docsite/rst/developing_plugins.rst @@ -1,8 +1,7 @@ Developing Plugins ================== -.. contents:: - :depth: 2 +.. contents:: Topics Ansible is pluggable in a lot of other ways separate from inventory scripts and callbacks. Many of these features are there to cover fringe use cases and are infrequently needed, and others are pluggable simply because they are there to implement core features in ansible and were most convenient to be made pluggable. @@ -15,14 +14,14 @@ as often. Connection Type Plugins ----------------------- -By default, ansible ships with a 'paramiko' SSH, native ssh (just called 'ssh'), and 'local' connection type, and an accelerated connection type named 'fireball' -- there are also some minor players like 'chroot' and 'jail'. All of these can be used +By default, ansible ships with a 'paramiko' SSH, native ssh (just called 'ssh'), 'local' connection type, and an accelerated connection type named 'fireball' (superseded in 1.3 by :doc:`playbooks_acceleration`) -- there are also some minor players like 'chroot' and 'jail'. All of these can be used in playbooks and with /usr/bin/ansible to decide how you want to talk to remote machines. The basics of these connection types -are covered in the 'getting started' section. Should you want to extend Ansible to support other transports (SNMP? Message bus? +are covered in the :doc:`intro_getting_started` section. Should you want to extend Ansible to support other transports (SNMP? Message bus? Carrier Pigeon?) it's as simple as copying the format of one of the existing modules and dropping it into the connection plugins directory. The value of 'smart' for a connection allows selection of paramiko or openssh based on system capabilities, and chooses 'ssh' if OpenSSH supports ControlPersist, in Ansible 1.2.1 an later. Previous versions did not support 'smart'. -More documentation on writing connection plugins is pending, though you can jump into lib/ansible/runner/connection_plugins and figure things out pretty easily. +More documentation on writing connection plugins is pending, though you can jump into `lib/ansible/runner/connection_plugins `_ and figure things out pretty easily. .. _developing_lookup_plugins: @@ -31,7 +30,7 @@ Lookup Plugins Language constructs like "with_fileglob" and "with_items" are implemented via lookup plugins. Just like other plugin types, you can write your own. -More documentation on writing connection plugins is pending, though you can jump into lib/ansible/runner/lookup_plugins and figure +More documentation on writing connection plugins is pending, though you can jump into `lib/ansible/runner/lookup_plugins `_ and figure things out pretty easily. .. _developing_vars_plugins: @@ -43,7 +42,7 @@ Playbook constructs like 'host_vars' and 'group_vars' work via 'vars' plugins. data into ansible runs that did not come from an inventory, playbook, or command line. Note that variables can also be returned from inventory, so in most cases, you won't need to write or understand vars_plugins. -More documentation on writing connection plugins is pending, though you can jump into lib/ansible/inventory/vars_plugins and figure +More documentation on writing connection plugins is pending, though you can jump into `lib/ansible/inventory/vars_plugins `_ and figure things out pretty easily. If you find yourself wanting to write a vars_plugin, it's more likely you should write an inventory script instead. @@ -53,9 +52,9 @@ If you find yourself wanting to write a vars_plugin, it's more likely you should Filter Plugins -------------- -If you want more Jinja2 filters available in a Jinja2 template (filters like to_yaml and to_json are provided by default), they can be extended by writing a filter plugin. Most of the time, when someone comes up with an idea for a new filter they would like to make available in a playbook, we'll just include them in 'core.py' instead. +If you want more Jinja2 filters available in a Jinja2 template (filters like to_yaml and to_json are provided by default), they can be extended by writing a filter plugin. Most of the time, when someone comes up with an idea for a new filter they would like to make available in a playbook, we'll just include them in 'core.py' instead. -Jump into lib/ansible/runner/filter_plugins/ for details. +Jump into `lib/ansible/runner/filter_plugins/ `_ for details. .. _developing_callbacks: @@ -69,27 +68,34 @@ Callbacks are one of the more interesting plugin types. Adding additional callb Examples ++++++++ -Example callbacks are shown `in github in the callbacks directory _`. +Example callbacks are shown in `plugins/callbacks `_. -The 'log_plays' callback is an example of how to intercept playbook events to a log file, and the 'mail' callback sends email -when playbooks complete. +The `log_plays +`_ +callback is an example of how to intercept playbook events to a log +file, and the `mail +`_ +callback sends email when playbooks complete. -The 'osx_say' callback provided is particularly entertaining -- it will respond with computer synthesized speech on OS X in relation -to playbook events, and is guaranteed to entertain and/or annoy coworkers. +The `osx_say +`_ +callback provided is particularly entertaining -- it will respond with +computer synthesized speech on OS X in relation to playbook events, +and is guaranteed to entertain and/or annoy coworkers. .. _configuring_callbacks: Configuring +++++++++++ -To active a callback drop it in a callback directory as configured in ansible.cfg. +To active a callback drop it in a callback directory as configured in :ref:`ansible.cfg `. .. _callback_development: Development +++++++++++ -More information will come later, though see the source of any of the existing callbacks and you should be able to get started quickly. +More information will come later, though see the source of any of the existing callbacks and you should be able to get started quickly. They should be reasonably self explanatory. .. _distributing_plugins: @@ -125,4 +131,3 @@ In addition, plugins can be shipped in a subdirectory relative to a top-level pl The development mailing list `irc.freenode.net `_ #ansible IRC chat channel - diff --git a/docsite/rst/faq.rst b/docsite/rst/faq.rst index 17653ee082..1cdabd9148 100644 --- a/docsite/rst/faq.rst +++ b/docsite/rst/faq.rst @@ -3,9 +3,6 @@ Frequently Asked Questions Here are some commonly-asked questions and their answers. -.. contents:: - :depth: 2 - .. _users_and_ports: How do I handle different machines needing different user accounts or ports to log in with? @@ -94,7 +91,7 @@ Where does the configuration file live and what can I configure in it? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -See `intro_configuration`. +See :doc:`intro_configuration`. .. _who_would_ever_want_to_disable_cowsay_but_ok_here_is_how: @@ -141,6 +138,42 @@ Then you can use the facts inside your template, like this:: {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }} {% endfor %} +.. _programatic_access_to_a_variable: + +How do I access a variable name programatically? +++++++++++++++++++++++++++++++++++++++++++++++++ + +An example may come up where we need to get the ipv4 address of an arbitrary interface, where the interface to be used may be supplied +via a role parameter or other input. Variable names can be built by adding strings together, like so:: + + {{ hostvars[inventory_hostname]['ansible_' + which_interface]['ipv4']['address'] }} + +The trick about going through hostvars is neccessary because it's a dictionary of the entire namespace of variables. 'inventory_hostname' +is a magic variable that indiciates the current host you are looping over in the host loop. + +.. _first_host_in_a_group: + +How do I access a variable of the first host in a group? +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +What happens if we want the ip address of the first webserver in the webservers group? Well, we can do that too. Note that if we +are using dynamic inventory, which host is the 'first' may not be consistent, so you wouldn't want to do this unless your inventory +was static and predictable. (If you are using AWX, it will use database order, so this isn't a problem even if you are using cloud +based inventory scripts). + +Anyway, here's the trick:: + + {{ hostvars[groups['webservers'][0]]['ansible_eth0']['ipv4']['address'] }} + +Notice how we're pulling out the hostname of the first machine of the webservers group. If you are doing this in a template, you +could use the Jinja2 '#set' directive to simplify this, or in a playbook, you could also use set_fact: + + - set_fact: headnode={{ groups[['webservers'][0]] }} + + - debug: msg={{ hostvars[headnode].ansible_eth0.ipv4.address }} + +Notice how we interchanged the bracket syntax for dots -- that can be done anywhere. + .. _file_recursion: How do I copy files recursively onto a target host? @@ -182,9 +215,19 @@ Ansible 1.4 will also make remote environment variables available via facts in t How do I generate crypted passwords for the user module? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -Crypted password values can be generated as follows:: +The mkpasswd utility that is available on most Linux systems is a great option:: - openssl passwd -salt -1 + mkpasswd --method=SHA-512 + +If this utility is not installed on your system (e.g. you are using OS X) then you can still easily +generate these passwords using Python. First, ensure that the `Passlib <https://code.google.com/p/passlib/>`_ +password hashing library is installed. + + pip install passlib + +Once the library is ready, SHA512 password values can then be generated as follows:: + + python -c "from passlib.hash import sha512_crypt; print sha512_crypt.encrypt('<password>')" .. _commercial_support: @@ -199,7 +242,7 @@ Is there a web interface / REST API / etc? ++++++++++++++++++++++++++++++++++++++++++ Yes! AnsibleWorks makes a great product that makes Ansible even more powerful -and easy to use: `AnsibleWorks AWX <http://ansibleworks.com/ansible-awx/>` +and easy to use: `AnsibleWorks AWX <http://ansibleworks.com/ansible-awx/>`_. .. _docs_contributions: @@ -215,7 +258,7 @@ I don't see my question here We're happy to help. -See the "Resources" section of the documentation home page for a link to the IRC and Google Group. +See the `Resources <http://www.ansibleworks.com/community/>`_ section of the documentation home page for a link to the IRC and Google Group. .. seealso:: diff --git a/docsite/rst/galaxy.rst b/docsite/rst/galaxy.rst new file mode 100644 index 0000000000..3bc0aa5304 --- /dev/null +++ b/docsite/rst/galaxy.rst @@ -0,0 +1,15 @@ +AnsibleWorks Galaxy +``````````````````` + +.. image:: https://galaxy.ansibleworks.com/static/img/galaxy_logo_small.png + :alt: AnsibleWorks Galaxy Logo + :width: 619px + :height: 109px + +`AnsibleWorks Galaxy <http://galaxy.ansibleworks.com>`_, is a free site for finding, downloading, rating, and reviewing all kinds of community developed Ansible roles and can be a great way to get a jumpstart on your automation projects. + +You can sign up with social auth, and the download client 'ansible-galaxy' is included in Ansible 1.4.2 and later. + +Read the "About" page on the Galaxy site for more information. + + diff --git a/docsite/rst/glossary.rst b/docsite/rst/glossary.rst index d64c26633e..352e808481 100644 --- a/docsite/rst/glossary.rst +++ b/docsite/rst/glossary.rst @@ -1,9 +1,6 @@ Glossary ======== -.. contents:: - :depth: 2 - The following is a list (and re-explanation) of term definitions used elsewhere in the Ansible documentation. Consult the documentation home page for the full documentation and to see the terms in context, but this should be a good resource @@ -59,8 +56,8 @@ Conditionals ++++++++++++ A conditional is an expression that evaluates to true or false that decides whether a given task will be executed on a given -machine or not. Ansible's conditionals include 'when_boolean', -'when_string', and 'when_integer'. These are discussed in the playbook documentation. +machine or not. Ansible's conditionals are powered by the 'when' statement, and are +discussed in the playbook documentation. Diff Mode +++++++++ @@ -223,11 +220,6 @@ JSON Ansible uses JSON for return data from remote modules. This allows modules to be written in any language, not just Python. -only_if -+++++++ - -A deprecated form of the "when:" statement. It should no longer be used. - Library +++++++ diff --git a/docsite/rst/guide_aws.rst b/docsite/rst/guide_aws.rst index 0573528548..7bbbbee61d 100644 --- a/docsite/rst/guide_aws.rst +++ b/docsite/rst/guide_aws.rst @@ -1,9 +1,6 @@ Amazon Web Services Guide ========================= -.. contents:: - :depth: 2 - .. _aws_intro: Introduction @@ -55,7 +52,7 @@ The ec2 module provides the ability to provision instances within EC2. Typicall exporting the variable as EC2_URL=https://myhost:8773/services/Eucalyptus. This can be set using the 'environment' keyword in Ansible if you like. -Here is an example of provisioning a number of instances in ad-hoc mode mode: +Here is an example of provisioning a number of instances in ad-hoc mode: .. code-block:: bash @@ -118,6 +115,22 @@ You may wish to schedule a regular refresh of the inventory cache to accommodate Put this into a crontab as appropriate to make calls from your Ansible master server to the EC2 API endpoints and gather host information. The aim is to keep the view of hosts as up-to-date as possible, so schedule accordingly. Playbook calls could then also be scheduled to act on the refreshed hosts inventory after each refresh. This approach means that machine images can remain "raw", containing no payload and OS-only. Configuration of the workload is handled entirely by Ansible. +Tags +++++ + +There's a feature in the ec2 inventory script where hosts tagged with +certain keys and values automatically appear in certain groups. + +For instance, if a host is given the "class" tag with the value of "webserver", +it will be automatically discoverable via a dynamic group like so:: + + - hosts: tag_class_webserver + tasks: + - ping + +Using this philosophy can be a great way to manage groups dynamically, without +having to maintain seperate inventory. + .. _aws_pull: Pull Configuration @@ -125,7 +138,7 @@ Pull Configuration For some the delay between refreshing host information and acting on that host information (i.e. running Ansible tasks against the hosts) may be too long. This may be the case in such scenarios where EC2 AutoScaling is being used to scale the number of instances as a result of a particular event. Such an event may require that hosts come online and are configured as soon as possible (even a 1 minute delay may be undesirable). Its possible to pre-bake machine images which contain the necessary ansible-pull script and components to pull and run a playbook via git. The machine images could be configured to run ansible-pull upon boot as part of the bootstrapping procedure. -More information on pull-mode playbooks can be found `here <http://www.ansibleworks.com/docs/playbooks2.html#pull-mode-playbooks>`_. +Read :ref:`ansible-pull` for more information on pull-mode playbooks. (Various developments around Ansible are also going to make this easier in the near future. Stay tuned!) diff --git a/docsite/rst/guide_rax.rst b/docsite/rst/guide_rax.rst index a4e984c60b..ae4a39b6bc 100644 --- a/docsite/rst/guide_rax.rst +++ b/docsite/rst/guide_rax.rst @@ -1,9 +1,6 @@ Rackspace Cloud Guide ===================== -.. contents:: - :depth: 3 - .. _introduction: Introduction @@ -100,7 +97,7 @@ provisioning task will be performed from your Ansible control server against the specifying your username and API key as environment variables or passing them as module arguments. -Here is a basic example of provisioning a instance in ad-hoc mode mode: +Here is a basic example of provisioning a instance in ad-hoc mode: .. code-block:: bash @@ -542,7 +539,7 @@ Build a complete webserver environment with servers, custom networks and load ba module: rax_clb_nodes credentials: ~/.raxpub load_balancer_id: "{{ clb.balancer.id }}" - address: "{{ item.networks.private|first }}" + address: "{{ item.rax_networks.private|first }}" port: 80 condition: enabled type: primary diff --git a/docsite/rst/guide_rolling_upgrade.rst b/docsite/rst/guide_rolling_upgrade.rst index e578006f7b..65f60ff441 100644 --- a/docsite/rst/guide_rolling_upgrade.rst +++ b/docsite/rst/guide_rolling_upgrade.rst @@ -1,9 +1,6 @@ Continuous Delivery and Rolling Upgrades ======================================== -.. contents:: - :depth: 2 - .. _lamp_introduction: Introduction @@ -78,7 +75,7 @@ as push updates to all of the servers:: - base-apache - nagios -.. note: +.. note:: If you're not familiar with terms like playbooks and plays, you should review :doc:`playbooks`. @@ -121,7 +118,7 @@ Here is lamp_haproxy's ``group_vars/all`` file. As you might expect, these varia This is a YAML file, and you can create lists and dictionaries for more complex variable structures. In this case, we are just setting two variables, one for the port for the web server, and one for the -NTP server that our machiens should use for time synchronization. +NTP server that our machines should use for time synchronization. Here's another group variables file. This is ``group_vars/dbservers`` which applies to the hosts in the ``dbservers`` group:: diff --git a/docsite/rst/guide_vagrant.rst b/docsite/rst/guide_vagrant.rst index 899735de85..4fb40d569f 100644 --- a/docsite/rst/guide_vagrant.rst +++ b/docsite/rst/guide_vagrant.rst @@ -1,9 +1,6 @@ Using Vagrant and Ansible ========================= -.. contents:: - :depth: 2 - .. _vagrant_intro: Introduction diff --git a/docsite/rst/guides.rst b/docsite/rst/guides.rst new file mode 100644 index 0000000000..05af9b023d --- /dev/null +++ b/docsite/rst/guides.rst @@ -0,0 +1,15 @@ +Detailed Guides +``````````````` + +This section is new and evolving. The idea here is explore particular use cases in greater depth and provide a more "top down" explanation of some basic features. + +.. toctree:: + :maxdepth: 1 + + guide_aws + guide_rax + guide_vagrant + guide_rolling_upgrade + +Pending topics may include: Docker, Jenkins, Google Compute Engine, Linode/Digital Ocean, Continous Deployment, and more. + diff --git a/docsite/rst/index.rst b/docsite/rst/index.rst index e3408e9f02..75eb96a416 100644 --- a/docsite/rst/index.rst +++ b/docsite/rst/index.rst @@ -18,187 +18,25 @@ upgrade remote daemons or the problem of not being able to manage systems becaus You may be interested in reading about `some notable Ansible users <http://www.ansibleworks.com/users/>`_. -This documentation covers the current released version of Ansible (1.4.3) and also some development version features (1.5). For recent features, in each section, the version of Ansible where the feature is added is indicated. AnsibleWorks releases a new major release of Ansible approximately every 2 months. The core application evolves somewhat conservatively, valuing simplicity in language design and setup, while the community around new modules and plugins being developed and contributed moves very very quickly, typically adding 20 or so new modules in each release. +This documentation covers the current released version of Ansible (1.4.4) and also some development version features (1.5). For recent features, in each section, the version of Ansible where the feature is added is indicated. AnsibleWorks releases a new major release of Ansible approximately every 2 months. The core application evolves somewhat conservatively, valuing simplicity in language design and setup, while the community around new modules and plugins being developed and contributed moves very very quickly, typically adding 20 or so new modules in each release. .. _an_introduction: -The Basics -`````````` - -Before we dive into the really fun parts -- playbooks, configuration management, deployment, and orchestration, we'll learn how to get Ansible installed and some basic concepts. We'll go over how to execute ad-hoc commands in parallel across your nodes using /usr/bin/ansible. We'll also see what sort of modules are available in Ansible's core (though you can also write your own, which we'll also show later). - -.. toctree:: - :maxdepth: 1 - - intro_installation - intro_getting_started - intro_inventory - intro_dynamic_inventory - intro_patterns - intro_adhoc - intro_configuration - -Modules -``````` - -Ansible modules are resources that are distributed to remote nodes to make them perform particular tasks or match a particular -state. Ansible follows a "batteries included" philosophy, so you have a lot of great modules for all manner of -IT tasks in the core distribution. This means modules are well up-to-date and you don't have to hunt for an implementation -that will work on your platform. You may think of the module library as a toolbox full of useful system management tools, -and playbooks as the instructions for building something using those tools. - -.. toctree:: - :maxdepth: 1 - - modules - -.. _overview: - -Architecture Diagram -```````````````````` - -.. image:: http://www.ansibleworks.com/wp-content/uploads/2013/06/ANSIBLE_DIAGRAM.jpg - :alt: ansible architecture diagram - :width: 788px - :height: 436px - -.. _introduction_to_playbooks: - -Playbooks -````````` - -Playbooks are Ansible's configuration, deployment, and orchestration language. They can describe a policy you want your remote systems to enforce, or a set of steps in a general IT process. - -If Ansible modules are the tools in your workshop, playbooks are your design plans. - -At a basic level, playbooks can be used to manage configurations of and deployments to remote machines. At a more advanced level, they can sequence multi-tier rollouts involving rolling updates, and can delegate actions to other hosts, interacting with monitoring servers and load balancers along the way. - -While there's a lot of information here, there's no need to learn everything at once. You can start small and pick up more features -over time as you need them. - -Playbooks are designed to be human-readable and are developed in a basic text language. There are multiple -ways to organize playbooks and the files they include, and we'll offer up some suggestions on that and making the most out of Ansible. - -It is recommended to look at `Example Playbooks <https://github.com/ansible/ansible-examples>`_ while reading along with the playbook documentation. These illustrate best practices as well as how to put many of the various concepts together. - .. toctree:: :maxdepth: 1 + intro + quickstart playbooks - playbooks_roles - playbooks_variables - playbooks_conditionals - playbooks_loops - playbooks_best_practices - -.. _advanced_topics_in_playbooks: - -Special Topics In Playbooks -``````````````````````````` - -Here are some playbook features that not everyone may need to learn, but can be quite useful for particular applications. -Browsing these topics is recommended as you may find some useful tips here, but feel free to learn the basics of Ansible first -and adopt these only if they seem relevant or useful to your environment. - -.. toctree:: - :maxdepth: 1 - - playbooks_acceleration - playbooks_async - playbooks_checkmode - playbooks_delegation - playbooks_environment - playbooks_error_handling - playbooks_lookups - playbooks_prompts - playbooks_tags - -.. _ansibleworks_awx: - -AnsibleWorks AWX -```````````````` - -`AnsibleWorks <http://ansibleworks.com>`_, who also sponsors the Ansible community, also produces 'AWX', which is a web-based solution that makes Ansible even more easy to use for IT teams of all kinds. It's designed to be the hub for all of your automation tasks. - -AWX allows you to control access to who can access what, even allowing sharing of SSH credentials without someone being able to transfer those credentials. Inventory can be graphically managed or synced with a wide variety of cloud sources. It logs all of your jobs, integrates well with LDAP, and has an amazing browsable REST API. Command line tools are available for easy integration -with Jenkins as well. - -Find out more about AWX features and how to download it on the `AWX webpage <http://ansibleworks.com/ansibleworks-awx>`_. AWX -is free for usage for up to 10 nodes, and comes bundled with amazing support from AnsibleWorks. As you would expect, AWX is -installed using Ansible playbooks! - -.. _ansibleworks_galaxy: - -AnsibleWorks Galaxy -``````````````````` - -.. image:: https://galaxy.ansibleworks.com/static/img/galaxy_logo_small.png - :alt: AnsibleWorks Galaxy Logo - :width: 619px - :height: 109px - -`AnsibleWorks Galaxy <http://galaxy.ansibleworks.com>`_, is a free site for finding, downloading, rating, and reviewing all kinds of community developed Ansible roles and can be a great way to get a jumpstart on your automation projects. - -You can sign up with social auth, and the download client 'ansible-galaxy' is included in Ansible 1.4.2 and later. - -Read the "About" page on the Galaxy site for more information. - -.. _detailed_guides: - -Detailed Guides -``````````````` - -This section is new and evolving. The idea here is explore particular use cases in greater depth and provide a more "top down" explanation of some basic features. - -.. toctree:: - :maxdepth: 1 - - guide_aws - guide_rax - guide_vagrant - guide_rolling_upgrade - -Pending topics may include: Docker, Jenkins, Google Compute Engine, Linode/Digital Ocean, Continous Deployment, and more. - -.. _community_information: - -Community Information -````````````````````` - -Ansible is an open source project designed to bring together developers and administrators of all kinds to collaborate on building -IT automation solutions that work well for them. Should you wish to get more involved -- whether in terms of just asking a question, helping other users, introducing new people to Ansible, or helping with the software or documentation, we welcome your contributions to the project. - -`Ways to interact <https://github.com/ansible/ansible/blob/devel/CONTRIBUTING.md>`_ - -.. _developer_information: - -Developer Information -````````````````````` - -Learn how to build modules of your own in any language, and also how to extend Ansible through several kinds of plugins. Explore Ansible's Python API and write Python plugins to integrate with other solutions in your environment. - -.. toctree:: - :maxdepth: 1 - - developing_api - developing_inventory - developing_modules - developing_plugins - -Developers will also likely be interested in the fully-discoverable `REST API <http://ansibleworks.com/ansibleworks-awx>`_ that is part of AnsibleWorks AWX. It's great for embedding Ansible in all manner of applications. - -.. _misc: - -Miscellaneous -````````````` - -Some additional topics you may be interested in: - -.. toctree:: - :maxdepth: 1 - + playbooks_special_topics + modules + modules_by_category + guides + developing + awx + community + galaxy faq glossary YAMLSyntax - diff --git a/docsite/rst/intro.rst b/docsite/rst/intro.rst new file mode 100644 index 0000000000..20518529e5 --- /dev/null +++ b/docsite/rst/intro.rst @@ -0,0 +1,17 @@ +Introduction +============ + +Before we dive into the really fun parts -- playbooks, configuration management, deployment, and orchestration, we'll learn how to get Ansible installed and some basic concepts. We'll go over how to execute ad-hoc commands in parallel across your nodes using /usr/bin/ansible. We'll also see what sort of modules are available in Ansible's core (though you can also write your own, which we'll also show later). + +.. toctree:: + :maxdepth: 1 + + intro_installation + intro_getting_started + intro_inventory + intro_dynamic_inventory + intro_patterns + intro_adhoc + intro_configuration + + diff --git a/docsite/rst/intro_adhoc.rst b/docsite/rst/intro_adhoc.rst index 25b38a1583..ba1033d061 100644 --- a/docsite/rst/intro_adhoc.rst +++ b/docsite/rst/intro_adhoc.rst @@ -1,6 +1,8 @@ Introduction To Ad-Hoc Commands =============================== +.. contents:: Topics + .. highlight:: bash The following examples show how to use `/usr/bin/ansible` for running @@ -31,9 +33,6 @@ port over directly to the playbook language. If you haven't read :doc:`intro_inventory` already, please look that over a bit first and then we'll get going. -.. contents:: - :depth: 2 - .. _parallelism_and_shell_commands: Parallelism and Shell Commands diff --git a/docsite/rst/intro_configuration.rst b/docsite/rst/intro_configuration.rst index f9eb3c9347..b4f675f414 100644 --- a/docsite/rst/intro_configuration.rst +++ b/docsite/rst/intro_configuration.rst @@ -1,6 +1,8 @@ The Ansible Configuration File ++++++++++++++++++++++++++++++ +.. contents:: Topics + .. highlight:: bash Certain things in Ansible are adjustable in a configuration file. In general, the stock configuration is probably @@ -15,9 +17,6 @@ The mechanism for doing this is the "ansible.cfg" file, which is looked for in t If multiple file locations matching the above exist, the last location on the above list is used. Settings in files are not merged together. -.. contents:: - :depth: 2 - .. _getting_the_latest_configuration: Getting the latest configuration diff --git a/docsite/rst/intro_dynamic_inventory.rst b/docsite/rst/intro_dynamic_inventory.rst index 52de7eb9a5..11a2dda9a5 100644 --- a/docsite/rst/intro_dynamic_inventory.rst +++ b/docsite/rst/intro_dynamic_inventory.rst @@ -3,6 +3,8 @@ Dynamic Inventory ================= +.. contents:: Topics + Often a user of a configuration management system will want to keep inventory in a different software system. Ansible provides a basic text-based system as described in :doc:`intro_inventory` but what if you want to use something else? @@ -16,9 +18,6 @@ Ansible easily supports all of these options via an external inventory system. For information about writing your own dynamic inventory source, see :doc:`developing_inventory`. -.. contents:: - :depth: 2 - .. _cobbler_example: diff --git a/docsite/rst/intro_getting_started.rst b/docsite/rst/intro_getting_started.rst index f0f7b48cea..ae1172d307 100644 --- a/docsite/rst/intro_getting_started.rst +++ b/docsite/rst/intro_getting_started.rst @@ -1,13 +1,12 @@ Getting Started =============== -.. contents:: - :depth: 2 +.. contents:: Topics .. _gs_about: -About -````` +Foreword +```````` Now that you've read :doc:`intro_installation` and installed Ansible, it's time to dig in and get started with some commands. diff --git a/docsite/rst/intro_installation.rst b/docsite/rst/intro_installation.rst index 4ef2d23ef9..39ffecc483 100644 --- a/docsite/rst/intro_installation.rst +++ b/docsite/rst/intro_installation.rst @@ -1,8 +1,7 @@ Installation ============ -.. contents:: - :depth: 2 +.. contents:: Topics .. _getting_ansible: diff --git a/docsite/rst/intro_inventory.rst b/docsite/rst/intro_inventory.rst index 3812ad35b8..cfed57489e 100644 --- a/docsite/rst/intro_inventory.rst +++ b/docsite/rst/intro_inventory.rst @@ -3,6 +3,8 @@ Inventory ========= +.. contents:: Topics + Ansible works against multiple systems in your infrastructure at the same time. It does this by selecting portions of systems listed in Ansible's inventory file, which defaults to being saved in @@ -12,9 +14,6 @@ Not only is this inventory configurable, but you can also use multiple inventory files at the same time (explained below) and also pull inventory from dynamic or cloud sources, as described in :doc:`intro_dynamic_inventory`. -.. contents:: - :depth: 2 - .. _inventoryformat: Hosts and Groups @@ -36,6 +35,9 @@ The format for /etc/ansible/hosts is an INI format and looks like this:: The things in brackets are group names, which are used in classifying systems and deciding what systems you are controlling at what times and for what purpose. +It is ok to put systems in more than one group, for instance a server could be both a webserver and a dbserver. +If you do, note that variables will come from all of the groups they are a member of, and variable precedence is detailed in a later chapter. + If you have hosts that run on non-standard SSH ports you can put the port number after the hostname with a colon. Ports listed in your SSH config file won't be used, so it is important that you set them if things are not running on the default port:: @@ -191,7 +193,7 @@ mentioned:: ansible_ssh_pass The ssh password to use (this is insecure, we strongly recommend using --ask-pass or SSH keys) ansible_sudo_pass - The sudo password to use (this is insecure, we strongly recommend using --ask-pass or SSH keys) + The sudo password to use (this is insecure, we strongly recommend using --ask-sudo-pass) ansible_connection Connection type of the host. Candidates are local, ssh or paramiko. The default is paramiko before Ansible 1.2, and 'smart' afterwards which detects whether usage of 'ssh' would be feasible based on whether ControlPersist is supported. ansible_ssh_private_key_file diff --git a/docsite/rst/intro_patterns.rst b/docsite/rst/intro_patterns.rst index f46a5f5a30..7d8ebe0f52 100644 --- a/docsite/rst/intro_patterns.rst +++ b/docsite/rst/intro_patterns.rst @@ -1,8 +1,7 @@ Patterns ++++++++ -.. contents:: - :depth: 2 +.. contents:: Topics Patterns in Ansible are how we decide which hosts to manage. This can mean what hosts to communicate with, but in terms of :doc:`playbooks` it actually means what hosts to apply a particular configuration or IT process to. diff --git a/docsite/rst/modules.rst b/docsite/rst/modules.rst index 800348e982..1e2a851d4a 100644 --- a/docsite/rst/modules.rst +++ b/docsite/rst/modules.rst @@ -1,19 +1,18 @@ -Ansible Modules -=============== +About Modules +============= -.. contents:: - :depth: 3 +.. toctree:: + :maxdepth: 4 .. _modules_intro: Introduction ```````````` - Ansible ships with a number of modules (called the 'module library') that can be executed directly on remote hosts or through :doc:`Playbooks <playbooks>`. -Users can also write their own modules. These modules can control system -resources, like services, packages, or files (anything really), or + +Users can also write their own modules. These modules can control system resources, like services, packages, or files (anything really), or handle executing system commands. Let's review how we execute three different modules from the command line:: @@ -23,65 +22,28 @@ Let's review how we execute three different modules from the command line:: ansible webservers -m command -a "/sbin/reboot -t now" Each module supports taking arguments. Nearly all modules take ``key=value`` -arguments, space delimited. Some modules take no arguments, and the -command/shell modules simply take the string of the command you want to run. +arguments, space delimited. Some modules take no arguments, and the command/shell modules simply +take the string of the command you want to run. From playbooks, Ansible modules are executed in a very similar way:: - name: reboot the servers action: command /sbin/reboot -t now -Version 0.8 and higher support the following shorter syntax:: +Which can be abbreviated to:: - name: reboot the servers command: /sbin/reboot -t now -All modules technically return JSON format data, though if you are using the -command line or playbooks, you don't really need to know much about -that. If you're writing your own module, you care, and this means you do -not have to write modules in any particular language -- you get to choose. +All modules technically return JSON format data, though if you are using the command line or playbooks, you don't really need to know much about +that. If you're writing your own module, you care, and this means you do not have to write modules in any particular language -- you get to choose. Modules are `idempotent`, meaning they will seek to avoid changes to the system unless a change needs to be made. When using Ansible -playbooks, these modules can trigger 'change events' in the form of notifying 'handlers' -to run additional tasks. +playbooks, these modules can trigger 'change events' in the form of notifying 'handlers' to run additional tasks. -Documentation for each module can be accessed from the command line with the -ansible-doc as well as the man command:: +Documentation for each module can be accessed from the command line with the ansible-doc tool:: - ansible-doc command - - man ansible.template - -Let's see what's available in the Ansible module library, out of the box: - - -.. include:: modules/_list.rst - -.. _ansible_doc: - -Reading Module Documentation Locally -```````````````````````````````````` - -ansible-doc is a friendly command line tool that allows you to access module documentation locally. -It comes with Ansible. - -To list documentation for a particular module:: - - ansible-doc yum | less - -To list all modules available:: - - ansible-doc --list | less - -To access modules outside of the stock module path (such as custom modules that live in your playbook directory), -use the '--module-path' option to specify the directory where the module lives. - -.. _writing_modules: - -Writing your own modules -```````````````````````` - -See :doc:`developing_modules`. + ansible-doc yum .. seealso:: diff --git a/docsite/rst/playbooks.rst b/docsite/rst/playbooks.rst index 6aa5f0cae6..20af4e02fc 100644 --- a/docsite/rst/playbooks.rst +++ b/docsite/rst/playbooks.rst @@ -1,396 +1,28 @@ -Intro to Playbooks -================== +Playbooks +````````` -.. contents:: - :depth: 2 +Playbooks are Ansible's configuration, deployment, and orchestration language. They can describe a policy you want your remote systems to enforce, or a set of steps in a general IT process. -.. _about_playbooks: +If Ansible modules are the tools in your workshop, playbooks are your design plans. -About Playbooks -``````````````` +At a basic level, playbooks can be used to manage configurations of and deployments to remote machines. At a more advanced level, they can sequence multi-tier rollouts involving rolling updates, and can delegate actions to other hosts, interacting with monitoring servers and load balancers along the way. -Playbooks are a completely different way to use ansible than in adhoc task execution mode, and are -particularly powerful. +While there's a lot of information here, there's no need to learn everything at once. You can start small and pick up more features +over time as you need them. -Simply put, playbooks are the basis for a really simple configuration management and multi-machine deployment system, -unlike any that already exist, and one that is very well suited to deploying complex applications. +Playbooks are designed to be human-readable and are developed in a basic text language. There are multiple +ways to organize playbooks and the files they include, and we'll offer up some suggestions on that and making the most out of Ansible. -Playbooks can declare configurations, but they can also orchestrate steps of -any manual ordered process, even as different steps must bounce back and forth -between sets of machines in particular orders. They can launch tasks -synchronously or asynchronously. +It is recommended to look at `Example Playbooks <https://github.com/ansible/ansible-examples>`_ while reading along with the playbook documentation. These illustrate best practices as well as how to put many of the various concepts together. -While you might run the main /usr/bin/ansible program for ad-hoc -tasks, playbooks are more likely to be kept in source control and used -to push out your configuration or assure the configurations of your -remote systems are in spec. +.. toctree:: + :maxdepth: 1 -There are also some full sets of playbooks illustrating a lot of these techniques in the -`ansible-examples repository <https://github.com/ansible/ansible-examples>`_. We'd recommend -looking at these in another tab as you go along. - -There are also many jumping off points after you learn playbooks, so hop back to the documentation -index after you're done with this section. - -.. _playbook_language_example: - -Playbook Language Example -````````````````````````` - -Playbooks are expressed in YAML format (see :doc:`YAMLSyntax`) and have a minimum of syntax, which intentionally -tries to not be a programming language or script, but rather a model of a configuration or a process. - -Each playbook is composed of one or more 'plays' in a list. - -The goal of a play is to map a group of hosts to some well defined roles, represented by -things ansible calls tasks. At a basic level, a task is nothing more than a call -to an ansible module, which you should have learned about in earlier chapters. - -By composing a playbook of multiple 'plays', it is possible to -orchestrate multi-machine deployments, running certain steps on all -machines in the webservers group, then certain steps on the database -server group, then more commands back on the webservers group, etc. - -"plays" are more or less a sports analogy. You can have quite a lot of plays that affect your systems -to do different things. It's not as if you were just defining one particular state or model, and you -can run different plays at different times. - -For starters, here's a playbook that contains just one play:: - - --- - - hosts: webservers - vars: - http_port: 80 - max_clients: 200 - remote_user: root - tasks: - - name: ensure apache is at the latest version - yum: pkg=httpd state=latest - - name: write the apache config file - template: src=/srv/httpd.j2 dest=/etc/httpd.conf - notify: - - restart apache - - name: ensure apache is running - service: name=httpd state=started - handlers: - - name: restart apache - service: name=httpd state=restarted - -Below, we'll break down what the various features of the playbook language are. - -.. _playbook_basics: - -Basics -`````` - -.. _playbook_hosts_and_users: - -Hosts and Users -+++++++++++++++ - -For each play in a playbook, you get to choose which machines in your infrastructure -to target and what remote user to complete the steps (called tasks) as. - -The `hosts` line is a list of one or more groups or host patterns, -separated by colons, as described in the :doc:`intro_patterns` -documentation. The `remote_user` is just the name of the user account:: - - --- - - hosts: webservers - remote_user: root - -.. Note:: - - The `remote_user` parameter was formerly called just `user`. It was renamed in Ansible 1.4 to make it more distinguishable from the `user` module (used to create users on remote systems). - -Remote users can also be defined per task:: - - --- - - hosts: webservers - remote_user: root - tasks: - - name: test connection - ping: - remote_user: yourname - -.. Note:: - - The `remote_user` parameter for tasks was added in 1.4. - - -Support for running things from sudo is also available:: - - --- - - hosts: webservers - remote_user: yourname - sudo: yes - -You can also use sudo on a particular task instead of the whole play:: - - --- - - hosts: webservers - remote_user: yourname - tasks: - - service: name=nginx state=started - sudo: yes - - -You can also login as you, and then sudo to different users than root:: - - --- - - hosts: webservers - remote_user: yourname - sudo: yes - sudo_user: postgres - -If you need to specify a password to sudo, run `ansible-playbook` with ``--ask-sudo-pass`` (`-K`). -If you run a sudo playbook and the playbook seems to hang, it's probably stuck at the sudo prompt. -Just `Control-C` to kill it and run it again with `-K`. - -.. important:: - - When using `sudo_user` to a user other than root, the module - arguments are briefly written into a random tempfile in /tmp. - These are deleted immediately after the command is executed. This - only occurs when sudoing from a user like 'bob' to 'timmy', not - when going from 'bob' to 'root', or logging in directly as 'bob' or - 'root'. If this concerns you that this data is briefly readable - (not writable), avoid transferring uncrypted passwords with - `sudo_user` set. In other cases, '/tmp' is not used and this does - not come into play. Ansible also takes care to not log password - parameters. - -.. _tasks_list: - -Tasks list -++++++++++ - -Each play contains a list of tasks. Tasks are executed in order, one -at a time, against all machines matched by the host pattern, -before moving on to the next task. It is important to understand that, within a play, -all hosts are going to get the same task directives. It is the purpose of a play to map -a selection of hosts to tasks. - -When running the playbook, which runs top to bottom, hosts with failed tasks are -taken out of the rotation for the entire playbook. If things fail, simply correct the playbook file and rerun. - -The goal of each task is to execute a module, with very specific arguments. -Variables, as mentioned above, can be used in arguments to modules. - -Modules are 'idempotent', meaning if you run them -again, they will make only the changes they must in order to bring the -system to the desired state. This makes it very safe to rerun -the same playbook multiple times. They won't change things -unless they have to change things. - -The `command` and `shell` modules will typically rerun the same command again, -which is totally ok if the command is something like -'chmod' or 'setsebool', etc. Though there is a 'creates' flag available which can -be used to make these modules also idempotent. - -Every task should have a `name`, which is included in the output from -running the playbook. This is output for humans, so it is -nice to have reasonably good descriptions of each task step. If the name -is not provided though, the string fed to 'action' will be used for -output. - -Tasks can be declared using the legacy "action: module options" format, but -it is recommended that you use the more conventional "module: options" format. -This recommended format is used throughout the documentation, but you may -encounter the older format in some playbooks. - -Here is what a basic task looks like, as with most modules, -the service module takes key=value arguments:: - - tasks: - - name: make sure apache is running - service: name=httpd state=running - -The `command` and `shell` modules are the one modules that just takes a list -of arguments, and don't use the key=value form. This makes -them work just like you would expect. Simple:: - - tasks: - - name: disable selinux - command: /sbin/setenforce 0 - -The command and shell module care about return codes, so if you have a command -whose successful exit code is not zero, you may wish to do this:: - - tasks: - - name: run this command and ignore the result - shell: /usr/bin/somecommand || /bin/true - -Or this:: - - tasks: - - name: run this command and ignore the result - shell: /usr/bin/somecommand - ignore_errors: True - - -If the action line is getting too long for comfort you can break it on -a space and indent any continuation lines:: - - tasks: - - name: Copy ansible inventory file to client - copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts - owner=root group=root mode=0644 - -Variables can be used in action lines. Suppose you defined -a variable called 'vhost' in the 'vars' section, you could do this:: - - tasks: - - name: create a virtual host file for {{ vhost }} - template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }} - -Those same variables are usable in templates, which we'll get to later. - -Now in a very basic playbook all the tasks will be listed directly in that play, though it will usually -make more sense to break up tasks using the 'include:' directive. We'll show that a bit later. - -.. _action_shorthand: - -Action Shorthand -```````````````` - -.. versionadded:: 0.8 - -Ansible prefers listing modules like this in 0.8 and later:: - - template: src=templates/foo.j2 dest=/etc/foo.conf - -You will notice in earlier versions, this was only available as:: - - action: template src=templates/foo.j2 dest=/etc/foo.conf - -The old form continues to work in newer versions without any plan of deprecation. - -.. _handlers: - -Handlers: Running Operations On Change -`````````````````````````````````````` - -As we've mentioned, modules are written to be 'idempotent' and can relay when -they have made a change on the remote system. Playbooks recognize this and -have a basic event system that can be used to respond to change. - -These 'notify' actions are triggered at the end of each block of tasks in a playbook, and will only be -triggered once even if notified by multiple different tasks. - -For instance, multiple resources may indicate -that apache needs to be restarted because they have changed a config file, -but apache will only be bounced once to avoid unnecessary restarts. - -Here's an example of restarting two services when the contents of a file -change, but only if the file changes:: - - - name: template configuration file - template: src=template.j2 dest=/etc/foo.conf - notify: - - restart memcached - - restart apache - -The things listed in the 'notify' section of a task are called -handlers. - -Handlers are lists of tasks, not really any different from regular -tasks, that are referenced by name. Handlers are what notifiers -notify. If nothing notifies a handler, it will not run. Regardless -of how many things notify a handler, it will run only once, after all -of the tasks complete in a particular play. - -Here's an example handlers section:: - - handlers: - - name: restart memcached - service: name=memcached state=restarted - - name: restart apache - service: name=apache state=restarted - -Handlers are best used to restart services and trigger reboots. You probably -won't need them for much else. - -.. note:: - Notify handlers are always run in the order written. - -Roles are described later on. It's worthwhile to point out that handlers are -automatically processed between 'pre_tasks', 'roles', 'tasks', and 'post_tasks' -sections. If you ever want to flush all the handler commands immediately though, -in 1.2 and later, you can:: - - tasks: - - shell: some tasks go here - - meta: flush_handlers - - shell: some other tasks - -In the above example any queued up handlers would be processed early when the 'meta' -statement was reached. This is a bit of a niche case but can come in handy from -time to time. - -.. _executing_a_playbook: - -Executing A Playbook -```````````````````` - -Now that you've learned playbook syntax, how do you run a playbook? It's simple. -Let's run a playbook using a parallelism level of 10:: - - ansible-playbook playbook.yml -f 10 - -.. _tips_and_tricks: - - -Ansible-Pull -```````````` - -Should you want to invert the architecture of Ansible, so that nodes check in to a central location, instead -of pushing configuration out to them, you can. - -Ansible-pull is a small script that will checkout a repo of configuration instructions from git, and then -run ansible-playbook against that content. - -Assuming you load balance your checkout location, ansible-pull scales essentially infinitely. - -Run 'ansible-pull --help' for details. - -There's also a `clever playbook <https://github.com/ansible/ansible-examples/blob/master/language_features/ansible_pull.yml>`_ available to using ansible in push mode to configure ansible-pull via a crontab! - -Tips and Tricks -``````````````` - -Look at the bottom of the playbook execution for a summary of the nodes that were targeted -and how they performed. General failures and fatal "unreachable" communication attempts are -kept separate in the counts. - -If you ever want to see detailed output from successful modules as well as unsuccessful ones, -use the '--verbose' flag. This is available in Ansible 0.5 and later. - -Ansible playbook output is vastly upgraded if the cowsay -package is installed. Try it! - -To see what hosts would be affected by a playbook before you run it, you -can do this:: - - ansible-playbook playbook.yml --list-hosts. - -.. seealso:: - - :doc:`YAMLSyntax` - Learn about YAML syntax - :doc:`playbooks_best_practices` - Various tips about managing playbooks in the real world - :doc:`index` - Hop back to the documentation index for a lot of special topics about playbooks - :doc:`modules` - Learn about available modules - :doc:`developing_modules` - Learn how to extend Ansible by writing your own modules - :doc:`intro_patterns` - Learn about how to select hosts - `Github examples directory <https://github.com/ansible/ansible-examples>`_ - Complete end-to-end playbook examples - `Mailing List <http://groups.google.com/group/ansible-project>`_ - Questions? Help? Ideas? Stop by the list on Google Groups + playbooks_intro + playbooks_roles + playbooks_variables + playbooks_conditionals + playbooks_loops + playbooks_best_practices diff --git a/docsite/rst/playbooks_acceleration.rst b/docsite/rst/playbooks_acceleration.rst index 52512c9e1d..2160cf6581 100644 --- a/docsite/rst/playbooks_acceleration.rst +++ b/docsite/rst/playbooks_acceleration.rst @@ -1,9 +1,6 @@ Accelerated Mode ================ -.. contents:: - :depth: 2 - .. versionadded:: 1.3 While OpenSSH using the ControlPersist feature is quite fast and scalable, there is a certain small amount of overhead involved in diff --git a/docsite/rst/playbooks_async.rst b/docsite/rst/playbooks_async.rst index 3a7013564a..d94a6986ba 100644 --- a/docsite/rst/playbooks_async.rst +++ b/docsite/rst/playbooks_async.rst @@ -1,9 +1,6 @@ Asynchronous Actions and Polling ================================ -.. contents:: - :depth: 2 - By default tasks in playbooks block, meaning the connections stay open until the task is done on each node. This may not always be desirable, or you may be running operations that take longer than the SSH timeout. diff --git a/docsite/rst/playbooks_best_practices.rst b/docsite/rst/playbooks_best_practices.rst index 49d14be55b..efa4a4cc32 100644 --- a/docsite/rst/playbooks_best_practices.rst +++ b/docsite/rst/playbooks_best_practices.rst @@ -5,8 +5,7 @@ Here are some tips for making the most of Ansible playbooks. You can find some example playbooks illustrating these best practices in our `ansible-examples repository <https://github.com/ansible/ansible-examples>`_. (NOTE: These may not use all of the features in the latest release, but are still an excellent reference!). -.. contents:: - :depth: 2 +.. contents:: Topics .. _content_organization: diff --git a/docsite/rst/playbooks_checkmode.rst b/docsite/rst/playbooks_checkmode.rst index 555f682494..cc34ff5bdf 100644 --- a/docsite/rst/playbooks_checkmode.rst +++ b/docsite/rst/playbooks_checkmode.rst @@ -1,11 +1,10 @@ Check Mode ("Dry Run") ====================== -.. contents:: - :depth: 2 - .. versionadded:: 1.1 +.. contents:: Topics + When ansible-playbook is executed with --check it will not make any changes on remote systems. Instead, any module instrumented to support 'check mode' (which contains most of the primary core modules, but it is not required that all modules do this) will report what changes they would have made rather than making them. Other modules that do not support check mode will also take no action, but just will not report what changes they might have made. diff --git a/docsite/rst/playbooks_conditionals.rst b/docsite/rst/playbooks_conditionals.rst index 84b6f1b135..b9894d6278 100644 --- a/docsite/rst/playbooks_conditionals.rst +++ b/docsite/rst/playbooks_conditionals.rst @@ -1,8 +1,8 @@ Conditionals ============ -.. contents:: - :depth: 2 +.. contents:: Topics + Often the result of a play may depend on the value of a variable, fact (something learned about the remote system), or previous task result. In some cases, the values of variables may depend on other variables. @@ -21,7 +21,7 @@ Sometimes you will want to skip a particular step on a particular host. This co as simple as not installing a certain package if the operating system is a particular version, or it could be something like performing some cleanup steps if a filesystem is getting full. -This is easy to do in Ansible, with the `when` clause, which contains a Jinja2 expression (see `playbooks_variables`). +This is easy to do in Ansible, with the `when` clause, which contains a Jinja2 expression (see :doc:`playbooks_variables`). It's actually pretty simple:: tasks: @@ -82,7 +82,7 @@ If a required variable has not been set, you can skip or fail using Jinja2's - shell: echo "I've got '{{ foo }}' and am not afraid to use it!" when: foo is defined - - fail: msg="Bailing out: this play requires 'bar'" + - fail: msg="Bailing out. this play requires 'bar'" when: bar is not defined This is especially useful in combination with the conditional import of vars @@ -125,7 +125,7 @@ Or with a role:: - { role: debian_stock_config, when: ansible_os_family == 'Debian' } You will note a lot of 'skipped' output by default in Ansible when using this approach on systems that don't match the criteria. -Read up on the 'group_by' module in the `modules` docs for a more streamlined way to accomplish the same thing. +Read up on the 'group_by' module in the :doc:`modules` docs for a more streamlined way to accomplish the same thing. Conditional Imports ``````````````````` @@ -193,10 +193,10 @@ The following example shows how to template out a configuration file that was ve - name: template a file template: src={{ item }} dest=/etc/myapp/foo.conf with_first_found: - files: + - files: - {{ ansible_distribution }}.conf - default.conf - paths: + paths: - search_location_one/somedir/ - /opt/other_location/somedir/ diff --git a/docsite/rst/playbooks_delegation.rst b/docsite/rst/playbooks_delegation.rst index 440228139a..535d2e97bb 100644 --- a/docsite/rst/playbooks_delegation.rst +++ b/docsite/rst/playbooks_delegation.rst @@ -1,8 +1,7 @@ Delegation, Rolling Updates, and Local Actions ============================================== -.. contents:: - :depth: 2 +.. contents:: Topics Being designed for multi-tier deployments since the beginning, Ansible is great at doing things on one host on behalf of another, or doing local steps with reference to some remote hosts. diff --git a/docsite/rst/playbooks_environment.rst b/docsite/rst/playbooks_environment.rst index 4d02013a3b..971765ab30 100644 --- a/docsite/rst/playbooks_environment.rst +++ b/docsite/rst/playbooks_environment.rst @@ -1,9 +1,6 @@ Setting the Environment (and Working With Proxies) ================================================== -.. contents:: - :depth: 2 - .. versionadded:: 1.1 It is quite possible that you may need to get package updates through a proxy, or even get some package diff --git a/docsite/rst/playbooks_error_handling.rst b/docsite/rst/playbooks_error_handling.rst index d64917b05d..af5c021da5 100644 --- a/docsite/rst/playbooks_error_handling.rst +++ b/docsite/rst/playbooks_error_handling.rst @@ -1,8 +1,7 @@ Error Handling In Playbooks =========================== -.. contents:: - :depth: 2 +.. contents:: Topics Ansible normally has defaults that make sure to check the return codes of commands and modules and it fails fast -- forcing an error to be dealt with unless you decide otherwise. diff --git a/docsite/rst/playbooks_intro.rst b/docsite/rst/playbooks_intro.rst new file mode 100644 index 0000000000..becead2ea0 --- /dev/null +++ b/docsite/rst/playbooks_intro.rst @@ -0,0 +1,395 @@ +Intro to Playbooks +================== + +.. _about_playbooks: + +About Playbooks +``````````````` + +Playbooks are a completely different way to use ansible than in adhoc task execution mode, and are +particularly powerful. + +Simply put, playbooks are the basis for a really simple configuration management and multi-machine deployment system, +unlike any that already exist, and one that is very well suited to deploying complex applications. + +Playbooks can declare configurations, but they can also orchestrate steps of +any manual ordered process, even as different steps must bounce back and forth +between sets of machines in particular orders. They can launch tasks +synchronously or asynchronously. + +While you might run the main /usr/bin/ansible program for ad-hoc +tasks, playbooks are more likely to be kept in source control and used +to push out your configuration or assure the configurations of your +remote systems are in spec. + +There are also some full sets of playbooks illustrating a lot of these techniques in the +`ansible-examples repository <https://github.com/ansible/ansible-examples>`_. We'd recommend +looking at these in another tab as you go along. + +There are also many jumping off points after you learn playbooks, so hop back to the documentation +index after you're done with this section. + +.. _playbook_language_example: + +Playbook Language Example +````````````````````````` + +Playbooks are expressed in YAML format (see :doc:`YAMLSyntax`) and have a minimum of syntax, which intentionally +tries to not be a programming language or script, but rather a model of a configuration or a process. + +Each playbook is composed of one or more 'plays' in a list. + +The goal of a play is to map a group of hosts to some well defined roles, represented by +things ansible calls tasks. At a basic level, a task is nothing more than a call +to an ansible module, which you should have learned about in earlier chapters. + +By composing a playbook of multiple 'plays', it is possible to +orchestrate multi-machine deployments, running certain steps on all +machines in the webservers group, then certain steps on the database +server group, then more commands back on the webservers group, etc. + +"plays" are more or less a sports analogy. You can have quite a lot of plays that affect your systems +to do different things. It's not as if you were just defining one particular state or model, and you +can run different plays at different times. + +For starters, here's a playbook that contains just one play:: + + --- + - hosts: webservers + vars: + http_port: 80 + max_clients: 200 + remote_user: root + tasks: + - name: ensure apache is at the latest version + yum: pkg=httpd state=latest + - name: write the apache config file + template: src=/srv/httpd.j2 dest=/etc/httpd.conf + notify: + - restart apache + - name: ensure apache is running + service: name=httpd state=started + handlers: + - name: restart apache + service: name=httpd state=restarted + +Below, we'll break down what the various features of the playbook language are. + +.. _playbook_basics: + +Basics +`````` + +.. _playbook_hosts_and_users: + +Hosts and Users ++++++++++++++++ + +For each play in a playbook, you get to choose which machines in your infrastructure +to target and what remote user to complete the steps (called tasks) as. + +The `hosts` line is a list of one or more groups or host patterns, +separated by colons, as described in the :doc:`intro_patterns` +documentation. The `remote_user` is just the name of the user account:: + + --- + - hosts: webservers + remote_user: root + +.. note:: + + The `remote_user` parameter was formerly called just `user`. It was renamed in Ansible 1.4 to make it more distinguishable from the `user` module (used to create users on remote systems). + +Remote users can also be defined per task:: + + --- + - hosts: webservers + remote_user: root + tasks: + - name: test connection + ping: + remote_user: yourname + +.. note:: + + The `remote_user` parameter for tasks was added in 1.4. + + +Support for running things from sudo is also available:: + + --- + - hosts: webservers + remote_user: yourname + sudo: yes + +You can also use sudo on a particular task instead of the whole play:: + + --- + - hosts: webservers + remote_user: yourname + tasks: + - service: name=nginx state=started + sudo: yes + + +You can also login as you, and then sudo to different users than root:: + + --- + - hosts: webservers + remote_user: yourname + sudo: yes + sudo_user: postgres + +If you need to specify a password to sudo, run `ansible-playbook` with ``--ask-sudo-pass`` (`-K`). +If you run a sudo playbook and the playbook seems to hang, it's probably stuck at the sudo prompt. +Just `Control-C` to kill it and run it again with `-K`. + +.. important:: + + When using `sudo_user` to a user other than root, the module + arguments are briefly written into a random tempfile in /tmp. + These are deleted immediately after the command is executed. This + only occurs when sudoing from a user like 'bob' to 'timmy', not + when going from 'bob' to 'root', or logging in directly as 'bob' or + 'root'. If this concerns you that this data is briefly readable + (not writable), avoid transferring uncrypted passwords with + `sudo_user` set. In other cases, '/tmp' is not used and this does + not come into play. Ansible also takes care to not log password + parameters. + +.. _tasks_list: + +Tasks list +++++++++++ + +Each play contains a list of tasks. Tasks are executed in order, one +at a time, against all machines matched by the host pattern, +before moving on to the next task. It is important to understand that, within a play, +all hosts are going to get the same task directives. It is the purpose of a play to map +a selection of hosts to tasks. + +When running the playbook, which runs top to bottom, hosts with failed tasks are +taken out of the rotation for the entire playbook. If things fail, simply correct the playbook file and rerun. + +The goal of each task is to execute a module, with very specific arguments. +Variables, as mentioned above, can be used in arguments to modules. + +Modules are 'idempotent', meaning if you run them +again, they will make only the changes they must in order to bring the +system to the desired state. This makes it very safe to rerun +the same playbook multiple times. They won't change things +unless they have to change things. + +The `command` and `shell` modules will typically rerun the same command again, +which is totally ok if the command is something like +'chmod' or 'setsebool', etc. Though there is a 'creates' flag available which can +be used to make these modules also idempotent. + +Every task should have a `name`, which is included in the output from +running the playbook. This is output for humans, so it is +nice to have reasonably good descriptions of each task step. If the name +is not provided though, the string fed to 'action' will be used for +output. + +Tasks can be declared using the legacy "action: module options" format, but +it is recommended that you use the more conventional "module: options" format. +This recommended format is used throughout the documentation, but you may +encounter the older format in some playbooks. + +Here is what a basic task looks like, as with most modules, +the service module takes key=value arguments:: + + tasks: + - name: make sure apache is running + service: name=httpd state=running + +The `command` and `shell` modules are the only modules that just take a list +of arguments and don't use the key=value form. This makes +them work as simply as you would expect:: + + tasks: + - name: disable selinux + command: /sbin/setenforce 0 + +The command and shell module care about return codes, so if you have a command +whose successful exit code is not zero, you may wish to do this:: + + tasks: + - name: run this command and ignore the result + shell: /usr/bin/somecommand || /bin/true + +Or this:: + + tasks: + - name: run this command and ignore the result + shell: /usr/bin/somecommand + ignore_errors: True + + +If the action line is getting too long for comfort you can break it on +a space and indent any continuation lines:: + + tasks: + - name: Copy ansible inventory file to client + copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts + owner=root group=root mode=0644 + +Variables can be used in action lines. Suppose you defined +a variable called 'vhost' in the 'vars' section, you could do this:: + + tasks: + - name: create a virtual host file for {{ vhost }} + template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }} + +Those same variables are usable in templates, which we'll get to later. + +Now in a very basic playbook all the tasks will be listed directly in that play, though it will usually +make more sense to break up tasks using the 'include:' directive. We'll show that a bit later. + +.. _action_shorthand: + +Action Shorthand +```````````````` + +.. versionadded:: 0.8 + +Ansible prefers listing modules like this in 0.8 and later:: + + template: src=templates/foo.j2 dest=/etc/foo.conf + +You will notice in earlier versions, this was only available as:: + + action: template src=templates/foo.j2 dest=/etc/foo.conf + +The old form continues to work in newer versions without any plan of deprecation. + +.. _handlers: + +Handlers: Running Operations On Change +`````````````````````````````````````` + +As we've mentioned, modules are written to be 'idempotent' and can relay when +they have made a change on the remote system. Playbooks recognize this and +have a basic event system that can be used to respond to change. + +These 'notify' actions are triggered at the end of each block of tasks in a playbook, and will only be +triggered once even if notified by multiple different tasks. + +For instance, multiple resources may indicate +that apache needs to be restarted because they have changed a config file, +but apache will only be bounced once to avoid unnecessary restarts. + +Here's an example of restarting two services when the contents of a file +change, but only if the file changes:: + + - name: template configuration file + template: src=template.j2 dest=/etc/foo.conf + notify: + - restart memcached + - restart apache + +The things listed in the 'notify' section of a task are called +handlers. + +Handlers are lists of tasks, not really any different from regular +tasks, that are referenced by name. Handlers are what notifiers +notify. If nothing notifies a handler, it will not run. Regardless +of how many things notify a handler, it will run only once, after all +of the tasks complete in a particular play. + +Here's an example handlers section:: + + handlers: + - name: restart memcached + service: name=memcached state=restarted + - name: restart apache + service: name=apache state=restarted + +Handlers are best used to restart services and trigger reboots. You probably +won't need them for much else. + +.. note:: + Notify handlers are always run in the order written. + +Roles are described later on. It's worthwhile to point out that handlers are +automatically processed between 'pre_tasks', 'roles', 'tasks', and 'post_tasks' +sections. If you ever want to flush all the handler commands immediately though, +in 1.2 and later, you can:: + + tasks: + - shell: some tasks go here + - meta: flush_handlers + - shell: some other tasks + +In the above example any queued up handlers would be processed early when the 'meta' +statement was reached. This is a bit of a niche case but can come in handy from +time to time. + +.. _executing_a_playbook: + +Executing A Playbook +```````````````````` + +Now that you've learned playbook syntax, how do you run a playbook? It's simple. +Let's run a playbook using a parallelism level of 10:: + + ansible-playbook playbook.yml -f 10 + +.. _ansible-pull: + +Ansible-Pull +```````````` + +Should you want to invert the architecture of Ansible, so that nodes check in to a central location, instead +of pushing configuration out to them, you can. + +Ansible-pull is a small script that will checkout a repo of configuration instructions from git, and then +run ansible-playbook against that content. + +Assuming you load balance your checkout location, ansible-pull scales essentially infinitely. + +Run 'ansible-pull --help' for details. + +There's also a `clever playbook <https://github.com/ansible/ansible-examples/blob/master/language_features/ansible_pull.yml>`_ available to using ansible in push mode to configure ansible-pull via a crontab! + +.. _tips_and_tricks: + +Tips and Tricks +``````````````` + +Look at the bottom of the playbook execution for a summary of the nodes that were targeted +and how they performed. General failures and fatal "unreachable" communication attempts are +kept separate in the counts. + +If you ever want to see detailed output from successful modules as well as unsuccessful ones, +use the '--verbose' flag. This is available in Ansible 0.5 and later. + +Ansible playbook output is vastly upgraded if the cowsay +package is installed. Try it! + +To see what hosts would be affected by a playbook before you run it, you +can do this:: + + ansible-playbook playbook.yml --list-hosts. + +.. seealso:: + + :doc:`YAMLSyntax` + Learn about YAML syntax + :doc:`playbooks_best_practices` + Various tips about managing playbooks in the real world + :doc:`index` + Hop back to the documentation index for a lot of special topics about playbooks + :doc:`modules` + Learn about available modules + :doc:`developing_modules` + Learn how to extend Ansible by writing your own modules + :doc:`intro_patterns` + Learn about how to select hosts + `Github examples directory <https://github.com/ansible/ansible-examples>`_ + Complete end-to-end playbook examples + `Mailing List <http://groups.google.com/group/ansible-project>`_ + Questions? Help? Ideas? Stop by the list on Google Groups + + + diff --git a/docsite/rst/playbooks_lookups.rst b/docsite/rst/playbooks_lookups.rst index a38232f5f9..52ddaedb19 100644 --- a/docsite/rst/playbooks_lookups.rst +++ b/docsite/rst/playbooks_lookups.rst @@ -1,15 +1,14 @@ Using Lookups ============= -.. contents:: - :depth: 2 - Lookup plugins allow access of data in Ansible from outside sources. This can include the filesystem but also external datastores. These values are then made available using the standard templating system in Ansible, and are typically used to load variables or templates with information from those systems. .. note:: This is considered an advanced feature, and many users will probably not rely on these features. +.. contents:: Topics + .. _getting_file_contents: Intro to Lookups: Getting File Contents diff --git a/docsite/rst/playbooks_loops.rst b/docsite/rst/playbooks_loops.rst index cb766e5d7f..66ba66dbc7 100644 --- a/docsite/rst/playbooks_loops.rst +++ b/docsite/rst/playbooks_loops.rst @@ -1,14 +1,13 @@ Loops ===== -.. contents:: - :depth: 2 - Often you'll want to do many things in one task, such as create a lot of users, install a lot of packages, or repeat a polling step until a certain result is reached. This chapter is all about how to use loops in playbooks. +.. contents:: Topics + .. _standard_loops: Standard Loops @@ -321,7 +320,7 @@ That's how! Using register with a loop `````````````````````````` -When using ``register`` with a loop the data strucutre placed in the variable during a loop, will contain a ``results`` attribute, that is a list of all responses from the module. +When using ``register`` with a loop the data structure placed in the variable during a loop, will contain a ``results`` attribute, that is a list of all responses from the module. Here is an example of using ``register`` with ``with_items``:: @@ -331,7 +330,7 @@ Here is an example of using ``register`` with ``with_items``:: - two register: echo -This differs from the data strucutre returned when using ``register`` without a loop:: +This differs from the data structure returned when using ``register`` without a loop:: { "changed": true, @@ -383,7 +382,7 @@ Subsequent loops over the registered variable to inspect the results may look li Writing Your Own Iterators `````````````````````````` -While you ordinarily shouldn't have to, should you wish to write your own ways to loop over arbitrary datastructures, you can read `developing_plugins` for some starter +While you ordinarily shouldn't have to, should you wish to write your own ways to loop over arbitrary datastructures, you can read :doc:`developing_plugins` for some starter information. Each of the above features are implemented as plugins in ansible, so there are many implementations to reference. .. seealso:: diff --git a/docsite/rst/playbooks_prompts.rst b/docsite/rst/playbooks_prompts.rst index 427420d9d1..30fa51a843 100644 --- a/docsite/rst/playbooks_prompts.rst +++ b/docsite/rst/playbooks_prompts.rst @@ -1,9 +1,6 @@ Prompts ======= -.. contents:: - :depth: 2 - When running a playbook, you may wish to prompt the user for certain input, and can do so with the 'vars_prompt' section. diff --git a/docsite/rst/playbooks_roles.rst b/docsite/rst/playbooks_roles.rst index 2ac055b42e..05fd681f26 100644 --- a/docsite/rst/playbooks_roles.rst +++ b/docsite/rst/playbooks_roles.rst @@ -1,8 +1,7 @@ Playbook Roles and Include Statements ===================================== -.. contents:: - :depth: 2 +.. contents:: Topics Introduction ```````````` diff --git a/docsite/rst/playbooks_special_topics.rst b/docsite/rst/playbooks_special_topics.rst new file mode 100644 index 0000000000..323bb16448 --- /dev/null +++ b/docsite/rst/playbooks_special_topics.rst @@ -0,0 +1,19 @@ +Playbooks: Special Topics +````````````````````````` +Here are some playbook features that not everyone may need to learn, but can be quite useful for particular applications. +Browsing these topics is recommended as you may find some useful tips here, but feel free to learn the basics of Ansible first +and adopt these only if they seem relevant or useful to your environment. + +.. toctree:: + :maxdepth: 1 + + playbooks_acceleration + playbooks_async + playbooks_checkmode + playbooks_delegation + playbooks_environment + playbooks_error_handling + playbooks_lookups + playbooks_prompts + playbooks_tags + diff --git a/docsite/rst/playbooks_tags.rst b/docsite/rst/playbooks_tags.rst index 600ffa584e..c48ced2090 100644 --- a/docsite/rst/playbooks_tags.rst +++ b/docsite/rst/playbooks_tags.rst @@ -1,9 +1,6 @@ Tags ==== -.. contents:: - :depth: 2 - If you have a large playbook it may become useful to be able to run a specific part of the configuration without running the whole playbook. diff --git a/docsite/rst/playbooks_variables.rst b/docsite/rst/playbooks_variables.rst index 729affe562..61e231ed11 100644 --- a/docsite/rst/playbooks_variables.rst +++ b/docsite/rst/playbooks_variables.rst @@ -1,6 +1,8 @@ Variables ========= +.. contents:: Topics + While automation exists to make it easier to make things repeatable, all of your systems are likely not exactly alike. All of your systems are likely not the same. On some systems you may want to set some behavior @@ -15,15 +17,12 @@ based on those variables. Variables in Ansible are how we deal with differences between systems. -Once understanding variables you'll also want to dig into `playbooks_conditionals` and `playbooks_loops`. +Once understanding variables you'll also want to dig into :doc:`playbooks_conditionals` and :doc:`playbooks_loops`. Useful things like the "group_by" module and the "when" conditional can also be used with variables, and to help manage differences between systems. It's highly recommended that you consult the ansible-examples github repository to see a lot of examples of variables put to use. -.. contents:: - :depth: 2 - .. _valid_variable_names: What Makes A Valid Variable Name @@ -50,7 +49,7 @@ a bit of a refresher. Often you'll want to set variables based on what groups a machine is in. For instance, maybe machines in Boston want to use 'boston.ntp.example.com' as an NTP server. -See the `intro_inventory` document for multiple ways on how to define variables in inventory. +See the :doc:`intro_inventory` document for multiple ways on how to define variables in inventory. .. _playbook_variables: @@ -68,11 +67,11 @@ This can be nice as it's right there when you are reading the playbook. .. _included_variables: Variables defined from included files and roles ------------------------------------------------ +``````````````````````````````````````````````` It turns out we've already talked about variables in another place too. -As described in `intro_roles`, variables can also be included in the playbook via include files, which may or may +As described in :doc:`playbooks_roles`, variables can also be included in the playbook via include files, which may or may not be part of an "Ansible Role". Usage of roles is preferred as it provides a nice organizational system. .. _about_jinja2: @@ -86,13 +85,13 @@ Ansible allows you to reference variables in your playbooks using the Jinja2 templating system. While you can do a lot of complex things in Jinja, only the basics are things you really need to learn at first. -For instance, in a simple template, you can do something like +For instance, in a simple template, you can do something like:: My amp goes to {{ max_amp_value }} And that will provide the most basic form of variable substitution. -This is also valid directly in playbooks, and you'll occasionally want to do things like: +This is also valid directly in playbooks, and you'll occasionally want to do things like:: template: src=foo.cfg.j2 dest={{ remote_install_path}}/foo.cfg @@ -111,7 +110,7 @@ it's more than that -- you can also read variables about other hosts. We'll sho Jinja2 Filters `````````````` -.. note: These are infrequently utilized features. Use them if they fit a use case you have, but this is optional knowledge. +.. note:: These are infrequently utilized features. Use them if they fit a use case you have, but this is optional knowledge. Filters in Jinja2 are a way of transforming template expressions from one kind of data into another. Jinja2 ships with many of these as documented on the official Jinja2 template documentation. @@ -191,9 +190,9 @@ To get the difference of 2 lists (items in 1 that don't exist in 2):: {{ list1 |difference(list2)}} -To get the symetric difference of 2 lists (items exclusive to each list):: +To get the symmetric difference of 2 lists (items exclusive to each list):: - {{ list1 |symetric_difference(list2)}} + {{ list1 |symmetric_difference(list2)}} .. _other_useful_filters: @@ -233,7 +232,7 @@ Hey Wait, A YAML Gotcha ``````````````````````` YAML syntax requires that if you start a value with {{ foo }} you quote the whole line, since it wants to be -sure you aren't trying to start a YAML dictionary. This is covered on the `YAMLSyntax` page. +sure you aren't trying to start a YAML dictionary. This is covered on the :doc:`YAMLSyntax` page. This won't work:: @@ -573,7 +572,7 @@ Registered Variables Another major use of variables is running a command and using the result of that command to save the result into a variable. The value of a task being executed in ansible can be saved in a variable and used later. See some examples of this in the -`playbooks_conditionals` chapter. +:doc:`playbooks_conditionals` chapter. While it's mentioned elsewhere in that document too, here's a quick syntax example:: @@ -650,8 +649,6 @@ A frequently used idiom is walking a group to find all IP addresses in that grou An example of this could include pointing a frontend proxy server to all of the app servers, setting up the correct firewall rules between servers, etc. -Just a few other 'magic' variables are available... There aren't many. - Additionally, *inventory_hostname* is the name of the hostname as configured in Ansible's inventory host file. This can be useful for when you don't want to rely on the discovered hostname `ansible_hostname` or for other mysterious reasons. If you have a long FQDN, *inventory_hostname_short* also contains the part up to the first @@ -740,7 +737,7 @@ or in a file as above. Conditional Imports ``````````````````` -.. note: this behavior is infrequently used in Ansible. You may wish to skip this section. The 'group_by' module as described in the module documentation is a better way to achieve this behavior in most cases. +.. note:: This behavior is infrequently used in Ansible. You may wish to skip this section. The 'group_by' module as described in the module documentation is a better way to achieve this behavior in most cases. Sometimes you will want to do certain things differently in a playbook based on certain criteria. Having one playbook that works on multiple platforms and OS versions is a good example. @@ -820,7 +817,7 @@ control you might want over values. First off, group variables are super powerful. Site wide defaults should be defined as a 'group_vars/all' setting. Group variables are generally placed alongside -your inventory file. They can also be returned by a dynamic inventory script (see `intro_dynamic_inventory`) or defined +your inventory file. They can also be returned by a dynamic inventory script (see :doc:`intro_dynamic_inventory`) or defined in things like AnsibleWorks AWX from the UI or API:: --- @@ -838,7 +835,7 @@ If for some crazy reason we wanted to tell just a specific host to use a specifi --- # file: /etc/ansible/host_vars/xyz.boston.example.com - ntp-server: override.example.com + ntp_server: override.example.com So that covers inventory and what you would normally set there. It's a great place for things that deal with geography or behavior. Since groups are frequently the entity that maps roles onto hosts, it is sometimes a shortcut to set variables on the group instead of defining them on a role. You could go either way. diff --git a/docsite/rst/quickstart.rst b/docsite/rst/quickstart.rst new file mode 100644 index 0000000000..c33e68773c --- /dev/null +++ b/docsite/rst/quickstart.rst @@ -0,0 +1,9 @@ +Quickstart Video +```````````````` + +We've recorded a short video that shows how to get started with Ansible that you may like to use alongside the documentation. + +The `quickstart video <http://ansibleworks.com/quickstart/>`_ is about 20 minutes long and will show you some of the basics about your +first steps with Ansible. + +Enjoy, and be sure to visit the rest of the documentation to learn more. diff --git a/examples/DOCUMENTATION.yaml b/examples/DOCUMENTATION.yml similarity index 100% rename from examples/DOCUMENTATION.yaml rename to examples/DOCUMENTATION.yml diff --git a/examples/ansible.cfg b/examples/ansible.cfg index a9306a6119..f1a24605f8 100644 --- a/examples/ansible.cfg +++ b/examples/ansible.cfg @@ -131,6 +131,16 @@ filter_plugins = /usr/share/ansible_plugins/filter_plugins # control_path = %(directory)s/%%h-%%r #control_path = %(directory)s/ansible-ssh-%%h-%%p-%%r +# Enabling pipelining reduces the number of SSH operations required to +# execute a module on the remote server. This can result in a significant +# performance improvement when enabled, however when using "sudo:" you must +# first disable 'requiretty' in /etc/sudoers +# +# By default, this option is disabled to preserve compatibility with +# sudoers configurations that have requiretty (the default on many distros). +# +#pipelining = False + # if True, make ansible use scp if the connection type is ssh # (default is sftp) #scp_if_ssh = True diff --git a/hacking/module_formatter.py b/hacking/module_formatter.py index 35fde1b1f6..2202ec038c 100755 --- a/hacking/module_formatter.py +++ b/hacking/module_formatter.py @@ -24,16 +24,20 @@ import yaml import codecs import json import ast -from jinja2 import Environment, FileSystemLoader import re import optparse import time import datetime import subprocess import cgi +from jinja2 import Environment, FileSystemLoader + import ansible.utils import ansible.utils.module_docs as module_docs +##################################################################################### +# constants and paths + # if a module is added in a version of Ansible older than this, don't print the version added information # in the module documentation because everyone is assumed to be running something newer than this already. TO_OLD_TO_BE_NOTABLE = 1.0 @@ -41,13 +45,12 @@ TO_OLD_TO_BE_NOTABLE = 1.0 # Get parent directory of the directory this script lives in MODULEDIR=os.path.abspath(os.path.join( os.path.dirname(os.path.realpath(__file__)), os.pardir, 'library' - )) -EXAMPLE_YAML=os.path.abspath(os.path.join( - os.path.dirname(os.path.realpath(__file__)), os.pardir, 'examples', 'DOCUMENTATION.yaml' - )) +)) -# There is a better way of doing this! -# TODO: somebody add U(text, http://foo.bar/) as described by Tim in #991 +# The name of the DOCUMENTATION template +EXAMPLE_YAML=os.path.abspath(os.path.join( + os.path.dirname(os.path.realpath(__file__)), os.pardir, 'examples', 'DOCUMENTATION.yml' +)) _ITALIC = re.compile(r"I\(([^)]+)\)") _BOLD = re.compile(r"B\(([^)]+)\)") @@ -55,56 +58,10 @@ _MODULE = re.compile(r"M\(([^)]+)\)") _URL = re.compile(r"U\(([^)]+)\)") _CONST = re.compile(r"C\(([^)]+)\)") -def latex_ify(text): - - t = _ITALIC.sub("\\I{" + r"\1" + "}", text) - t = _BOLD.sub("\\B{" + r"\1" + "}", t) - t = _MODULE.sub("\\M{" + r"\1" + "}", t) - t = _URL.sub("\\url{" + r"\1" + "}", t) - t = _CONST.sub("\\C{" + r"\1" + "}", t) - - return t - -def html_ify(text): - - #print "DEBUG: text=%s" % text - - t = cgi.escape(text) - t = _ITALIC.sub("<em>" + r"\1" + "</em>", t) - t = _BOLD.sub("<b>" + r"\1" + "</b>", t) - t = _MODULE.sub("<span class='module'>" + r"\1" + "</span>", t) - t = _URL.sub("<a href='" + r"\1" + "'>" + r"\1" + "</a>", t) - t = _CONST.sub("<code>" + r"\1" + "</code>", t) - - return t - -def json_ify(text): - - t = _ITALIC.sub("<em>" + r"\1" + "</em>", text) - t = _BOLD.sub("<b>" + r"\1" + "</b>", t) - t = _MODULE.sub("<span class='module'>" + r"\1" + "</span>", t) - t = _URL.sub("<a href='" + r"\1" + "'>" + r"\1" + "</a>", t) - t = _CONST.sub("<code>" + r"\1" + "</code>", t) - - return t - - -def js_ify(text): - - return text - - -def man_ify(text): - - t = _ITALIC.sub(r'\\fI' + r"\1" + r"\\fR", text) - t = _BOLD.sub(r'\\fB' + r"\1" + r"\\fR", t) - t = _MODULE.sub(r'\\fI' + r"\1" + r"\\fR", t) - t = _URL.sub(r'\\fI' + r"\1" + r"\\fR", t) - t = _CONST.sub(r'\\fC' + r"\1" + r"\\fR", t) - - return t +##################################################################################### def rst_ify(text): + ''' convert symbols like I(this is in italics) to valid restructured text ''' t = _ITALIC.sub(r'*' + r"\1" + r"*", text) t = _BOLD.sub(r'**' + r"\1" + r"**", t) @@ -114,31 +71,40 @@ def rst_ify(text): return t -_MARKDOWN = re.compile(r"[*_`]") +##################################################################################### -def markdown_ify(text): +def html_ify(text): + ''' convert symbols like I(this is in italics) to valid HTML ''' t = cgi.escape(text) - t = _MARKDOWN.sub(r"\\\g<0>", t) - t = _ITALIC.sub("_" + r"\1" + "_", t) - t = _BOLD.sub("**" + r"\1" + "**", t) - t = _MODULE.sub("*" + r"\1" + "*", t) - t = _URL.sub("[" + r"\1" + "](" + r"\1" + ")", t) - t = _CONST.sub("`" + r"\1" + "`", t) + t = _ITALIC.sub("<em>" + r"\1" + "</em>", t) + t = _BOLD.sub("<b>" + r"\1" + "</b>", t) + t = _MODULE.sub("<span class='module'>" + r"\1" + "</span>", t) + t = _URL.sub("<a href='" + r"\1" + "'>" + r"\1" + "</a>", t) + t = _CONST.sub("<code>" + r"\1" + "</code>", t) return t -# Helper for Jinja2 (format() doesn't work here...) + +##################################################################################### + def rst_fmt(text, fmt): + ''' helper for Jinja2 to do format strings ''' + return fmt % (text) +##################################################################################### + def rst_xline(width, char="="): + ''' return a restructured text line of a given length ''' + return char * width -def load_examples_section(text): - return text.split('***BREAK***') +##################################################################################### + +def write_data(text, options, outputname, module): + ''' dumps module output to a file or the screen, as requested ''' -def return_data(text, options, outputname, module): if options.output_dir is not None: f = open(os.path.join(options.output_dir, outputname % module), 'w') f.write(text.encode('utf-8')) @@ -146,16 +112,12 @@ def return_data(text, options, outputname, module): else: print text -def boilerplate(): - if not os.path.exists(EXAMPLE_YAML): - print >>sys.stderr, "Missing example boiler plate: %s" % EXAMPLE_YAML - print "DOCUMENTATION = '''" - print file(EXAMPLE_YAML).read() - print "'''" - print "" +##################################################################################### def list_modules(module_dir): - categories = {} + ''' returns a hash of categories, each category being a hash of module names to file paths ''' + + categories = dict(all=dict()) files = glob.glob("%s/*" % module_dir) for d in files: if os.path.isdir(d): @@ -167,272 +129,195 @@ def list_modules(module_dir): if not category in categories: categories[category] = {} categories[category][module] = f + categories['all'][module] = f return categories -def main(): +##################################################################################### + +def generate_parser(): + ''' generate an optparse parser ''' p = optparse.OptionParser( version='%prog 1.0', usage='usage: %prog [options] arg1 arg2', - description='Convert Ansible module DOCUMENTATION strings to other formats', + description='Generate module documentation from metadata', ) - p.add_option("-A", "--ansible-version", - action="store", - dest="ansible_version", - default="unknown", - help="Ansible version number") - p.add_option("-M", "--module-dir", - action="store", - dest="module_dir", - default=MODULEDIR, - help="Ansible modules/ directory") - p.add_option("-T", "--template-dir", - action="store", - dest="template_dir", - default="hacking/templates", - help="directory containing Jinja2 templates") - p.add_option("-t", "--type", - action='store', - dest='type', - choices=['html', 'latex', 'man', 'rst', 'json', 'markdown', 'js'], - default='latex', - help="Output type") - p.add_option("-m", "--module", - action='append', - default=[], - dest='module_list', - help="Add modules to process in module_dir") - p.add_option("-v", "--verbose", - action='store_true', - default=False, - help="Verbose") - p.add_option("-o", "--output-dir", - action="store", - dest="output_dir", - default=None, - help="Output directory for module files") - p.add_option("-I", "--includes-file", - action="store", - dest="includes_file", - default=None, - help="Create a file containing list of processed modules") - p.add_option("-G", "--generate", - action="store_true", - dest="do_boilerplate", - default=False, - help="generate boilerplate DOCUMENTATION to stdout") + p.add_option("-A", "--ansible-version", action="store", dest="ansible_version", default="unknown", help="Ansible version number") + p.add_option("-M", "--module-dir", action="store", dest="module_dir", default=MODULEDIR, help="Ansible library path") + p.add_option("-T", "--template-dir", action="store", dest="template_dir", default="hacking/templates", help="directory containing Jinja2 templates") + p.add_option("-t", "--type", action='store', dest='type', choices=['html', 'latex', 'man', 'rst', 'json', 'markdown', 'js'], default='latex', help="Document type") + p.add_option("-v", "--verbose", action='store_true', default=False, help="Verbose") + p.add_option("-o", "--output-dir", action="store", dest="output_dir", default=None, help="Output directory for module files") + p.add_option("-I", "--includes-file", action="store", dest="includes_file", default=None, help="Create a file containing list of processed modules") p.add_option('-V', action='version', help='Show version number and exit') + return p - (options, args) = p.parse_args() +##################################################################################### -# print "M: %s" % options.module_dir -# print "t: %s" % options.type -# print "m: %s" % options.module_list -# print "v: %s" % options.verbose +def jinja2_environment(template_dir, typ): - if options.do_boilerplate: - boilerplate() - - print "" - print "EXAMPLES = '''" - print "# example of doing ___ from a playbook" - print "your_module: some_arg=1 other_arg=2" - print "'''" - print "" - - sys.exit(0) - - if not options.module_dir: - print "Need module_dir" - sys.exit(1) - if not os.path.exists(options.module_dir): - print >>sys.stderr, "Module directory does not exist: %s" % options.module_dir - sys.exit(1) - - - if not options.template_dir: - print "Need template_dir" - sys.exit(1) - - env = Environment(loader=FileSystemLoader(options.template_dir), + env = Environment(loader=FileSystemLoader(template_dir), variable_start_string="@{", variable_end_string="}@", trim_blocks=True, ) - env.globals['xline'] = rst_xline - if options.type == 'latex': - env.filters['jpfunc'] = latex_ify - template = env.get_template('latex.j2') - outputname = "%s.tex" - includecmt = "" - includefmt = "%s\n" - if options.type == 'html': - env.filters['jpfunc'] = html_ify - template = env.get_template('html.j2') - outputname = "%s.html" - includecmt = "" - includefmt = "" - if options.type == 'man': - env.filters['jpfunc'] = man_ify - template = env.get_template('man.j2') - outputname = "ansible.%s.3" - includecmt = "" - includefmt = "" - if options.type == 'rst': - env.filters['jpfunc'] = rst_ify + if typ == 'rst': + env.filters['convert_symbols_to_format'] = rst_ify env.filters['html_ify'] = html_ify env.filters['fmt'] = rst_fmt env.filters['xline'] = rst_xline template = env.get_template('rst.j2') - outputname = "%s.rst" - includecmt = ".. Generated by module_formatter\n" - includefmt = ".. include:: modules/%s.rst\n" - if options.type == 'json': - env.filters['jpfunc'] = json_ify - outputname = "%s.json" - includecmt = "" - includefmt = "" - if options.type == 'js': - env.filters['jpfunc'] = js_ify - template = env.get_template('js.j2') - outputname = "%s.js" - if options.type == 'markdown': - env.filters['jpfunc'] = markdown_ify - env.filters['html_ify'] = html_ify - template = env.get_template('markdown.j2') - outputname = "%s.md" - includecmt = "" - includefmt = "" + outputname = "%s_module.rst" + else: + raise Exception("unknown module format type: %s" % typ) - if options.includes_file is not None and includefmt != "": - incfile = open(options.includes_file, "w") - incfile.write(includecmt) + return env, template, outputname - # Temporary variable required to genrate aggregated content in 'js' format. - js_data = [] +##################################################################################### + +def process_module(module, options, env, template, outputname, module_map): + + print "rendering: %s" % module + + fname = module_map[module] + + # ignore files with extensions + if os.path.basename(fname).find(".") != -1: + return + + # use ansible core library to parse out doc metadata YAML and plaintext examples + doc, examples = ansible.utils.module_docs.get_docstring(fname, verbose=options.verbose) + + # crash if module is missing documentation and not explicitly hidden from docs index + if doc is None and module not in ansible.utils.module_docs.BLACKLIST_MODULES: + sys.stderr.write("*** ERROR: CORE MODULE MISSING DOCUMENTATION: %s, %s ***\n" % (fname, module)) + sys.exit(1) + if doc is None: + return "SKIPPED" + + all_keys = [] + + if not 'version_added' in doc: + sys.stderr.write("*** ERROR: missing version_added in: %s ***\n" % module) + sys.exit(1) + + added = 0 + if doc['version_added'] == 'historical': + del doc['version_added'] + else: + added = doc['version_added'] + + # don't show version added information if it's too old to be called out + if added: + added_tokens = str(added).split(".") + added = added_tokens[0] + "." + added_tokens[1] + added_float = float(added) + if added and added_float < TO_OLD_TO_BE_NOTABLE: + del doc['version_added'] + + for (k,v) in doc['options'].iteritems(): + all_keys.append(k) + all_keys = sorted(all_keys) + doc['option_keys'] = all_keys + + doc['filename'] = fname + doc['docuri'] = doc['module'].replace('_', '-') + doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') + doc['ansible_version'] = options.ansible_version + doc['plainexamples'] = examples #plain text + + # here is where we build the table of contents... + + text = template.render(doc) + write_data(text, options, outputname, module) + +##################################################################################### + +def process_category(category, categories, options, env, template, outputname): + + module_map = categories[category] + + category_file_path = os.path.join(options.output_dir, "list_of_%s_modules.rst" % category) + category_file = open(category_file_path, "w") + print "*** recording category %s in %s ***" % (category, category_file_path) + + # TODO: start a new category file + + category = category.replace("_"," ") + category = category.title() + + modules = module_map.keys() + modules.sort() + + category_header = "%s Modules" % (category.title()) + underscores = "`" * len(category_header) + + category_file.write("""\ +%s +%s + +.. toctree:: + :maxdepth: 1 + +""" % (category_header, underscores)) + + for module in modules: + result = process_module(module, options, env, template, outputname, module_map) + if result != "SKIPPED": + category_file.write(" %s_module\n" % module) + + + category_file.close() + + # TODO: end a new category file + +##################################################################################### + +def validate_options(options): + ''' validate option parser options ''' + + if not options.module_dir: + print >>sys.stderr, "--module-dir is required" + sys.exit(1) + if not os.path.exists(options.module_dir): + print >>sys.stderr, "--module-dir does not exist: %s" % options.module_dir + sys.exit(1) + if not options.template_dir: + print "--template-dir must be specified" + sys.exit(1) + +##################################################################################### + +def main(): + + p = generate_parser() + + (options, args) = p.parse_args() + validate_options(options) + + env, template, outputname = jinja2_environment(options.template_dir, options.type) categories = list_modules(options.module_dir) last_category = None category_names = categories.keys() category_names.sort() - + + category_list_path = os.path.join(options.output_dir, "modules_by_category.rst") + category_list_file = open(category_list_path, "w") + category_list_file.write("Module Index\n") + category_list_file.write("============\n") + category_list_file.write("\n\n") + category_list_file.write(".. toctree::\n") + category_list_file.write(" :maxdepth: 1\n\n") + for category in category_names: - module_map = categories[category] - - category = category.replace("_"," ") - category = category.title() + category_list_file.write(" list_of_%s_modules\n" % category) + process_category(category, categories, options, env, template, outputname) - modules = module_map.keys() - modules.sort() - - for module in modules: - - print "rendering: %s" % module - - fname = module_map[module] - - if len(options.module_list): - if not module in options.module_list: - continue - - # fname = os.path.join(options.module_dir, module) - - extra = os.path.join("inc", "%s.tex" % module) - - # probably could just throw out everything with extensions - if fname.endswith(".swp") or fname.endswith(".orig") or fname.endswith(".rej"): - continue - - # print " processing module source ---> %s" % fname - - if options.type == 'js': - if fname.endswith(".json"): - f = open(fname) - j = json.load(f) - f.close() - js_data.append(j) - continue - - doc, examples = ansible.utils.module_docs.get_docstring(fname, verbose=options.verbose) - - if doc is None and module not in ansible.utils.module_docs.BLACKLIST_MODULES: - print " while processing module source ---> %s" % fname - sys.stderr.write("*** ERROR: CORE MODULE MISSING DOCUMENTATION: %s ***\n" % module) - #sys.exit(1) - - if not doc is None: - - all_keys = [] - - if not 'version_added' in doc: - sys.stderr.write("*** ERROR: missing version_added in: %s ***\n" % module) - sys.exit(1) - - added = 0 - if doc['version_added'] == 'historical': - del doc['version_added'] - else: - added = doc['version_added'] - - # don't show version added information if it's too old to be called out - if added: - added_tokens = str(added).split(".") - added = added_tokens[0] + "." + added_tokens[1] - added_float = float(added) - if added and added_float < TO_OLD_TO_BE_NOTABLE: - del doc['version_added'] - - for (k,v) in doc['options'].iteritems(): - all_keys.append(k) - all_keys = sorted(all_keys) - doc['option_keys'] = all_keys - - doc['filename'] = fname - doc['docuri'] = doc['module'].replace('_', '-') - doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') - doc['ansible_version'] = options.ansible_version - doc['plainexamples'] = examples #plain text - - # BOOKMARK: here is where we build the table of contents... - - if options.includes_file is not None and includefmt != "": - - if last_category != category: - incfile.write("\n\n") - incfile.write(category) - incfile.write("\n") - incfile.write('`' * len(category)) - incfile.write("\n\n") - last_category = category - - incfile.write(includefmt % module) - - if options.verbose: - print json.dumps(doc, indent=4) - - - if options.type == 'latex': - if os.path.exists(extra): - f = open(extra) - extradata = f.read() - f.close() - doc['extradata'] = extradata - - if options.type == 'json': - text = json.dumps(doc, indent=2) - else: - text = template.render(doc) - - return_data(text, options, outputname, module) - - if options.type == 'js': - docs = {} - docs['json'] = json.dumps(js_data, indent=2) - text = template.render(docs) - return_data(text, options, outputname, 'modules') + category_list_file.close() if __name__ == '__main__': main() diff --git a/hacking/templates/html.j2 b/hacking/templates/html.j2 deleted file mode 100644 index b86e47378b..0000000000 --- a/hacking/templates/html.j2 +++ /dev/null @@ -1,7 +0,0 @@ -<!-- @{ module | upper }@ --> - -<h2>@{module}@</h2> - - {% for desc in description -%} - @{ desc | jpfunc }@ - {% endfor %} diff --git a/hacking/templates/js.j2 b/hacking/templates/js.j2 deleted file mode 100644 index fa2d4f7f53..0000000000 --- a/hacking/templates/js.j2 +++ /dev/null @@ -1,5 +0,0 @@ -function AnsibleModules($scope) { - $scope.modules = @{ json }@; - - $scope.orderProp = "module"; -} diff --git a/hacking/templates/latex.j2 b/hacking/templates/latex.j2 deleted file mode 100644 index d02ffa8677..0000000000 --- a/hacking/templates/latex.j2 +++ /dev/null @@ -1,76 +0,0 @@ -{# ------------------------------------------------------------------- - template for module_formatter.py for LaTeX output (Ansible Booklet) - by Jan-Piet Mens. - Note: nodes & code examples are omitted on purpose. - -------------------------------------------------------------------- #} -%--- @{ module | upper }@ ---- from @{ filename }@ --- - -%: -- module header -\mods{@{module}@}{@{docuri}@}{ - {% for desc in description -%} - @{ desc | jpfunc }@ - {% endfor -%} - {% if version_added is defined -%} - (\I{* new in version @{ version_added }@}) - {% endif -%} - } - -%: -- module options - - - -{% if options %} -\begin{xlist}{abcdefghijklmno} - {% for (opt,v) in options.iteritems() %} - {% if v['required'] %} - \item[\man\,\C{@{ opt }@}] - {% else %} - \item[\opt\,\C{@{ opt }@}] - {% endif %} - - {# -------- option description ----------#} - {% for desc in v.description %} - @{ desc | jpfunc }@ - {% endfor %} - {% if v['choices'] %} - \B{Choices}:\, - {% for choice in v['choices'] %}\C{@{ choice }@}{% if not loop.last %},{% else %}.{% endif %} - {% endfor %} - {% endif %} - {% if v['default'] %} - (default \C{@{ v['default'] }@}) - {% endif %} - {% if v['version_added'] is defined %} - (\I{* version @{ v['version_added'] }@}) - {% endif %} - {% endfor %} -\end{xlist} -{% endif %} - -{# --------------------------------------- -{% if notes %} - - {% for note in notes %} - \I{@{ note | jpfunc }@} - {% endfor %} -{% endif %} - ----------------------------- #} - -{#------------------------------------------- - -{% if examples is defined -%} - {% for e in examples %} - \begin{extymeta} -@{ e['code'] }@ - \end{extymeta} - {% endfor %} -{% endif %} ------------------------------------ #} - -{% if extradata is defined %} -%--- BEGIN-EXTRADATA -\begin{extymeta} -@{ extradata }@ -\end{extymeta} -%----- END-EXTRADATA -{% endif %} diff --git a/hacking/templates/man.j2 b/hacking/templates/man.j2 deleted file mode 100644 index 6cf51960ae..0000000000 --- a/hacking/templates/man.j2 +++ /dev/null @@ -1,74 +0,0 @@ -.TH ANSIBLE.@{ module | upper }@ 3 "@{ now_date }@" "@{ ansible_version }@" "ANSIBLE MODULES" -.\" generated from @{ filename }@ -.SH NAME -@{ module }@ \- @{ short_description }@ -.\" ------ DESCRIPTION -.SH DESCRIPTION -{% for desc in description %} -.PP -@{ desc | jpfunc }@ -{% endfor %} -.\" ------ OPTIONS -.\" -.\" -{% if options %} -.SH OPTIONS -{% for k in option_keys %} - {% set v = options[k] %} - -.IP @{ k }@ -{% for desc in v.description %}@{ desc | jpfunc }@{% endfor %} -{% if v.get('choices') %} - -.IR Choices : -{% for choice in v.get('choices',[]) %}{% if choice == True %}yes{%elif choice == False %}no{% else %}@{ choice }@{% endif %}{% if not loop.last %},{%else%}.{%endif%}{% endfor %}{% endif %} -{% if v.get('required') %}(required){% endif %} -{% if v.get('default') %} (default: {% if v.get('default') == True %}yes{%elif v.get('default') == False %}no{% else %}@{ v.get('default') }@){% endif %}{% endif %} -{#---------------------------------------------- #} -{% if v.get('version_added') %} -(Added in Ansible version @{ v.get('version_added') }@.) -{% endif %} -{% endfor %} -{% endif %} -.\" -.\" -.\" ------ NOTES -{% if notes %} -.SH NOTES -{% for note in notes %} -.PP -@{ note | jpfunc }@ -{% endfor %} -{% endif %} -.\" -.\" -.\" ------ EXAMPLES -{% if examples is defined %} -.SH EXAMPLES -{% for e in examples %} -.PP -{% if e['description'] %} -@{ e['description'] | jpfunc }@ -{% endif %} - -.nf -@{ e['code'] }@ -.fi -{% endfor %} -{% endif %} -.\" ------ PLAINEXAMPLES -{% if plainexamples is defined %} -.SH EXAMPLES -.nf -@{ plainexamples }@ -.fi -{% endif %} - -.\" ------- AUTHOR -{% if author is defined %} -.SH AUTHOR -@{ author }@ -{% endif %} -.SH SEE ALSO -.IR ansible (1), -.I http://ansible.github.com/modules.html#@{docuri}@ diff --git a/hacking/templates/markdown.j2 b/hacking/templates/markdown.j2 deleted file mode 100644 index 6f9bb0b3d8..0000000000 --- a/hacking/templates/markdown.j2 +++ /dev/null @@ -1,64 +0,0 @@ -## @{ module | jpfunc }@ - -{# ------------------------------------------ - # - # This is Github-flavored Markdown - # - --------------------------------------------#} - -{% if version_added is defined -%} -New in version @{ version_added }@. -{% endif %} - -{% for desc in description -%} -@{ desc | jpfunc }@ - -{% endfor %} - -{% if options -%} -<table> -<tr> -<th class="head">parameter</th> -<th class="head">required</th> -<th class="head">default</th> -<th class="head">choices</th> -<th class="head">comments</th> -</tr> -{% for (k,v) in options.iteritems() %} -<tr> -<td>@{ k }@</td> -<td>{% if v.get('required', False) %}yes{% else %}no{% endif %}</td> -<td>{% if v['default'] %}@{ v['default'] }@{% endif %}</td> -<td><ul>{% for choice in v.get('choices',[]) -%}<li>@{ choice }@</li>{% endfor -%}</ul></td> -<td>{% for desc in v.description -%}@{ desc | html_ify }@{% endfor -%}{% if v['version_added'] %} (added in Ansible @{v['version_added']}@){% endif %}</td> -</tr> -{% endfor %} -</table> -{% endif %} - -{% if examples or plainexamples %} -#### Examples -{% endif %} - -{% for example in examples %} -{% if example['description'] %} -* @{ example['description'] | jpfunc }@ -{% endif %} - -``` -@{ example['code'] }@ -``` -{% endfor %} -{% if plainexamples -%} -``` -@{ plainexamples }@ -``` -{% endif %} - -{% if notes %} -#### Notes -{% for note in notes %} -@{ note | jpfunc }@ -{% endfor %} -{% endif %} - diff --git a/hacking/templates/rst.j2 b/hacking/templates/rst.j2 index b4afcd1cab..5c39314476 100644 --- a/hacking/templates/rst.j2 +++ b/hacking/templates/rst.j2 @@ -1,7 +1,22 @@ .. _@{ module }@: -@{ module }@ -++++++++++++++++++++++++++++++++++++++ +{% if short_description %} +{% set title = module + ' - ' + short_description|convert_symbols_to_format %} +{% else %} +{% set title = module %} +{% endif %} +{% set title_len = title|length %} + +@{ title }@ +@{ '+' * title_len }@ + +{% if author %} +:Author: @{ author }@ +{% endif %} + +.. contents:: + :local: + :depth: 1 {# ------------------------------------------ # @@ -10,18 +25,24 @@ # --------------------------------------------#} +Synopsis +-------- + {% if version_added is defined -%} .. versionadded:: @{ version_added }@ {% endif %} {% for desc in description -%} -@{ desc | jpfunc }@ +@{ desc | convert_symbols_to_format }@ {% endfor %} {% if options -%} +Options +------- + .. raw:: html - <table> + <table border=1 cellpadding=4> <tr> <th class="head">parameter</th> <th class="head">required</th> @@ -47,17 +68,17 @@ {% endif %} {% if requirements %} -.. raw:: html +{% for req in requirements %} - <p> - <b>Requirements:</b> - {% for req in requirements %} - @{ req | html_ify }@ - {% endfor %} - </p> +.. note:: Requires @{ req | convert_symbols_to_format }@ +{% endfor %} {% endif %} +{% if examples or plainexamples %} +Examples +-------- + .. raw:: html {% for example in examples %} @@ -71,20 +92,16 @@ <br/> {% if plainexamples %} -.. raw:: html - <pre> -@{ plainexamples | escape | indent(4, True) }@ - </pre> +:: + +@{ plainexamples | indent(4, True) }@ +{% endif %} {% endif %} - {% if notes %} -.. raw:: html - - <h4>Notes</h4> - {% for note in notes %} - <p>@{ note | html_ify }@</p> - {% endfor %} +{% for note in notes %} +.. note:: @{ note | convert_symbols_to_format }@ +{% endfor %} {% endif %} diff --git a/lib/ansible/__init__.py b/lib/ansible/__init__.py index 7486c49b46..bad06025a1 100644 --- a/lib/ansible/__init__.py +++ b/lib/ansible/__init__.py @@ -1,4 +1,4 @@ -# (c) 2012-2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/callback_plugins/noop.py b/lib/ansible/callback_plugins/noop.py index 8af5a133e6..3d03ba55d6 100644 --- a/lib/ansible/callback_plugins/noop.py +++ b/lib/ansible/callback_plugins/noop.py @@ -1,4 +1,4 @@ -# (C) 2012-2013, Michael DeHaan, <michael.dehaan@gmail.com> +# (C) 2012-2014, Michael DeHaan, <michael.dehaan@gmail.com> # This file is part of Ansible # diff --git a/lib/ansible/callbacks.py b/lib/ansible/callbacks.py index 5274a6f8a5..4681dd2fe0 100644 --- a/lib/ansible/callbacks.py +++ b/lib/ansible/callbacks.py @@ -1,4 +1,4 @@ -# (C) 2012-2013, Michael DeHaan, <michael.dehaan@gmail.com> +# (C) 2012-2014, Michael DeHaan, <michael.dehaan@gmail.com> # This file is part of Ansible # diff --git a/lib/ansible/color.py b/lib/ansible/color.py index 304967993c..e5f6f4d2ba 100644 --- a/lib/ansible/color.py +++ b/lib/ansible/color.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index 2a08dd93d7..bf9084ef99 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # @@ -146,6 +146,7 @@ DEPRECATION_WARNINGS = get_config(p, DEFAULTS, 'deprecation_warnings', # CONNECTION RELATED ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) ANSIBLE_SSH_CONTROL_PATH = get_config(p, 'ssh_connection', 'control_path', 'ANSIBLE_SSH_CONTROL_PATH', "%(directory)s/ansible-ssh-%%h-%%p-%%r") +ANSIBLE_SSH_PIPELINING = get_config(p, 'ssh_connection', 'pipelining', 'ANSIBLE_SSH_PIPELINING', False, boolean=False) PARAMIKO_RECORD_HOST_KEYS = get_config(p, 'paramiko_connection', 'record_host_keys', 'ANSIBLE_PARAMIKO_RECORD_HOST_KEYS', True, boolean=True) ZEROMQ_PORT = get_config(p, 'fireball_connection', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099, integer=True) ACCELERATE_PORT = get_config(p, 'accelerate', 'accelerate_port', 'ACCELERATE_PORT', 5099, integer=True) diff --git a/lib/ansible/errors.py b/lib/ansible/errors.py index acbec4714f..344762f343 100644 --- a/lib/ansible/errors.py +++ b/lib/ansible/errors.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/inventory/__init__.py b/lib/ansible/inventory/__init__.py index f202a14192..e7979011cd 100644 --- a/lib/ansible/inventory/__init__.py +++ b/lib/ansible/inventory/__init__.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # @@ -133,11 +133,7 @@ class Inventory(object): # exclude hosts not in a subset, if defined if self._subset: subset = self._get_hosts(self._subset) - new_hosts = [] - for h in hosts: - if h in subset and h not in new_hosts: - new_hosts.append(h) - hosts = new_hosts + hosts = [ h for h in hosts if h in subset ] # exclude hosts mentioned in any restriction (ex: failed hosts) if self._restriction is not None: @@ -183,9 +179,7 @@ class Inventory(object): elif p.startswith("&"): hosts = [ h for h in hosts if h in that ] else: - for h in that: - if h not in hosts: - hosts.append(h) + hosts.extend([ h for h in that if h.name not in [ y.name for y in hosts ] ]) return hosts @@ -257,6 +251,8 @@ class Inventory(object): """ Get all host names matching the pattern """ hosts = [] + hostnames = set() + # ignore any negative checks here, this is handled elsewhere pattern = pattern.replace("!","").replace("&", "") @@ -265,8 +261,9 @@ class Inventory(object): for group in groups: for host in group.get_hosts(): if pattern == 'all' or self._match(group.name, pattern) or self._match(host.name, pattern): - if host not in results: + if host not in results and host.name not in hostnames: results.append(host) + hostnames.add(host.name) return results def clear_pattern_cache(self): @@ -351,7 +348,7 @@ class Inventory(object): vars.update(host.get_variables()) if self.parser is not None: - vars.update(self.parser.get_host_variables(host)) + vars = utils.combine_vars(vars, self.parser.get_host_variables(host)) return vars def add_group(self, group): diff --git a/lib/ansible/inventory/group.py b/lib/ansible/inventory/group.py index 6fc812dc0c..c5270ad554 100644 --- a/lib/ansible/inventory/group.py +++ b/lib/ansible/inventory/group.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/inventory/host.py b/lib/ansible/inventory/host.py index fea867382e..19b919ac66 100644 --- a/lib/ansible/inventory/host.py +++ b/lib/ansible/inventory/host.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/inventory/ini.py b/lib/ansible/inventory/ini.py index 69689758aa..220e842f6b 100644 --- a/lib/ansible/inventory/ini.py +++ b/lib/ansible/inventory/ini.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # @@ -89,10 +89,10 @@ class InventoryParser(object): # 0. A hostname that contains a range pesudo-code and a port # 1. A hostname that contains just a port if hostname.count(":") > 1: - # probably an IPv6 addresss, so check for the format - # XXX:XXX::XXX.port, otherwise we'll just assume no - # port is set - if hostname.find(".") != -1: + # Possible an IPv6 address, or maybe a host line with multiple ranges + # IPv6 with Port XXX:XXX::XXX.port + # FQDN foo.example.com + if hostname.count(".") == 1: (hostname, port) = hostname.rsplit(".", 1) elif (hostname.find("[") != -1 and hostname.find("]") != -1 and diff --git a/lib/ansible/inventory/script.py b/lib/ansible/inventory/script.py index 9474c3ebd7..05348b6839 100644 --- a/lib/ansible/inventory/script.py +++ b/lib/ansible/inventory/script.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/inventory/vars_plugins/group_vars.py b/lib/ansible/inventory/vars_plugins/group_vars.py index 2f5ee70146..3473b3192c 100644 --- a/lib/ansible/inventory/vars_plugins/group_vars.py +++ b/lib/ansible/inventory/vars_plugins/group_vars.py @@ -1,4 +1,4 @@ -# (c) 2012-2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/module_common.py b/lib/ansible/module_common.py index 7beac36bee..da02882d93 100644 --- a/lib/ansible/module_common.py +++ b/lib/ansible/module_common.py @@ -1,4 +1,4 @@ -# (c) 2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2013-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 1f4c625087..29766efb3c 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -706,6 +706,12 @@ class AnsibleModule(object): self.params[k] = int(value) else: is_invalid = True + elif wanted == 'float': + if not isinstance(value, float): + if isinstance(value, basestring): + self.params[k] = float(value) + else: + is_invalid = True else: self.fail_json(msg="implementation error: unknown type %s requested for %s" % (wanted, k)) diff --git a/lib/ansible/module_utils/ec2.py b/lib/ansible/module_utils/ec2.py index 0698c89056..4e8fa11f0f 100644 --- a/lib/ansible/module_utils/ec2.py +++ b/lib/ansible/module_utils/ec2.py @@ -1,3 +1,13 @@ +AWS_REGIONS = ['ap-northeast-1', + 'ap-southeast-1', + 'ap-southeast-2', + 'eu-west-1', + 'sa-east-1', + 'us-east-1', + 'us-west-1', + 'us-west-2'] + + def get_ec2_creds(module): # Check module args for credentials, then check environment vars @@ -36,3 +46,27 @@ def get_ec2_creds(module): region = os.environ['AWS_REGION'] return ec2_url, ec2_access_key, ec2_secret_key, region + + +def ec2_connect(module): + + """ Return an ec2 connection""" + + ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) + + # If we have a region specified, connect to its endpoint. + if region: + try: + ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) + except boto.exception.NoAuthHandlerFound, e: + module.fail_json(msg = str(e)) + # Otherwise, no region so we fallback to the old connection method + elif ec2_url: + try: + ec2 = boto.connect_ec2_endpoint(ec2_url, aws_access_key, aws_secret_key) + except boto.exception.NoAuthHandlerFound, e: + module.fail_json(msg = str(e)) + else: + module.fail_json(msg="Either region or ec2_url must be specified") + return ec2 + diff --git a/lib/ansible/module_utils/known_hosts.py b/lib/ansible/module_utils/known_hosts.py new file mode 100644 index 0000000000..b1b8affce0 --- /dev/null +++ b/lib/ansible/module_utils/known_hosts.py @@ -0,0 +1,67 @@ +def add_git_host_key(module, url, accept_hostkey=True): + + """ idempotently add a git url hostkey """ + + fqdn = get_fqdn(module.params['repo']) + + if fqdn: + known_host = check_hostkey(module, fqdn) + if not known_host: + if accept_hostkey: + rc, out, err = add_host_key(module, fqdn) + if rc != 0: + module.fail_json(msg="failed to add %s hostkey: %s" % (fqdn, out + err)) + else: + module.fail_json(msg="%s has an unknown hostkey. Set accept_hostkey to True or manually add the hostkey prior to running the git module" % fqdn) + +def get_fqdn(repo_url): + + """ chop the hostname out of a giturl """ + + result = None + if "@" in repo_url and not repo_url.startswith("http"): + repo_url = repo_url.split("@", 1)[1] + if ":" in repo_url: + repo_url = repo_url.split(":")[0] + result = repo_url + elif "/" in repo_url: + repo_url = repo_url.split("/")[0] + result = repo_url + + return result + + +def check_hostkey(module, fqdn): + + """ use ssh-keygen to check if key is known """ + + result = False + keygen_cmd = module.get_bin_path('ssh-keygen', True) + this_cmd = keygen_cmd + " -H -F " + fqdn + rc, out, err = module.run_command(this_cmd) + + if rc == 0: + if out != "": + result = True + else: + # Check the main system location + this_cmd = keygen_cmd + " -H -f /etc/ssh/ssh_known_hosts -F " + fqdn + rc, out, err = module.run_command(this_cmd) + + if rc == 0: + if out != "": + result = True + + return result + +def add_host_key(module, fqdn, key_type="rsa"): + + """ use ssh-keyscan to add the hostkey """ + + result = False + keyscan_cmd = module.get_bin_path('ssh-keyscan', True) + this_cmd = "%s -t %s %s >> ~/.ssh/known_hosts" % (keyscan_cmd, key_type, fqdn) + rc, out, err = module.run_command(this_cmd) + + return rc, out, err + diff --git a/lib/ansible/module_utils/rax.py b/lib/ansible/module_utils/rax.py index ddb5193ae4..edbeca43cf 100644 --- a/lib/ansible/module_utils/rax.py +++ b/lib/ansible/module_utils/rax.py @@ -3,7 +3,7 @@ import os def rax_argument_spec(): return dict( - api_key=dict(type='str'), + api_key=dict(type='str', no_log=True), credentials=dict(type='str', aliases=['creds_file']), region=dict(type='str'), username=dict(type='str'), diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index 36788f2409..dc7991aaf7 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -1,4 +1,4 @@ -# (c) 2012-2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # @@ -161,7 +161,7 @@ class PlayBook(object): play_basedirs = [] if type(playbook_data) != list: - raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list") + raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list, got %s" % type(playbook_data)) basedir = os.path.dirname(path) or '.' utils.plugins.push_basedir(basedir) @@ -310,7 +310,7 @@ class PlayBook(object): remote_port=task.play.remote_port, module_vars=task.module_vars, default_vars=task.default_vars, private_key_file=self.private_key_file, setup_cache=self.SETUP_CACHE, basedir=task.play.basedir, - conditional=task.only_if, callbacks=self.runner_callbacks, + conditional=task.when, callbacks=self.runner_callbacks, sudo=task.sudo, sudo_user=task.sudo_user, transport=task.transport, sudo_pass=task.sudo_pass, is_playbook=True, check=self.check, diff=self.diff, environment=task.environment, complex_args=task.args, @@ -433,14 +433,10 @@ class PlayBook(object): def _do_setup_step(self, play): ''' get facts from the remote system ''' - host_list = self._list_available_hosts(play.hosts) - if play.gather_facts is False: return {} - elif play.gather_facts is None: - host_list = [h for h in host_list if h not in self.SETUP_CACHE or 'module_setup' not in self.SETUP_CACHE[h]] - if len(host_list) == 0: - return {} + + host_list = self._list_available_hosts(play.hosts) self.callbacks.on_setup() self.inventory.restrict_to(host_list) diff --git a/lib/ansible/playbook/play.py b/lib/ansible/playbook/play.py index a2fdc643f2..92a40ba6ec 100644 --- a/lib/ansible/playbook/play.py +++ b/lib/ansible/playbook/play.py @@ -1,4 +1,4 @@ -# (c) 2012-2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # @@ -114,7 +114,7 @@ class Play(object): self.sudo = ds.get('sudo', self.playbook.sudo) self.sudo_user = ds.get('sudo_user', self.playbook.sudo_user) self.transport = ds.get('connection', self.playbook.transport) - self.gather_facts = ds.get('gather_facts', None) + self.gather_facts = ds.get('gather_facts', True) self.remote_port = self.remote_port self.any_errors_fatal = utils.boolean(ds.get('any_errors_fatal', 'false')) self.accelerate = utils.boolean(ds.get('accelerate', 'false')) @@ -484,21 +484,16 @@ class Play(object): include_vars = {} for k in x: if k.startswith("with_"): - utils.deprecated("include + with_items is an unsupported feature and has been undocumented for many releases because of this", "1.5") - plugin_name = k[5:] - if plugin_name not in utils.plugins.lookup_loader: - raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name)) - terms = template(self.basedir, x[k], task_vars) - items = utils.plugins.lookup_loader.get(plugin_name, basedir=self.basedir, runner=None).run(terms, inject=task_vars) + utils.deprecated("include + with_items is a removed deprecated feature", "1.5", removed=True) elif k.startswith("when_"): - included_additional_conditions.insert(0, utils.compile_when_to_only_if("%s %s" % (k[5:], x[k]))) + utils.deprecated("\"when_<criteria>:\" is a removed deprecated feature, use the simplified 'when:' conditional directly", None, removed=True) elif k == 'when': if type(x[k]) is str: - included_additional_conditions.insert(0, utils.compile_when_to_only_if("jinja2_compare %s" % x[k])) + included_additional_conditions.insert(0, x[k]) elif type(x[k]) is list: for i in x[k]: - included_additional_conditions.insert(0, utils.compile_when_to_only_if("jinja2_compare %s" % i)) - elif k in ("include", "vars", "default_vars", "only_if", "sudo", "sudo_user", "role_name"): + included_additional_conditions.insert(0, i) + elif k in ("include", "vars", "default_vars", "sudo", "sudo_user", "role_name"): continue else: include_vars[k] = x[k] @@ -516,8 +511,8 @@ class Play(object): if 'vars' in x: task_vars = utils.combine_vars(task_vars, x['vars']) - if 'only_if' in x: - included_additional_conditions.append(x['only_if']) + if 'when' in x: + included_additional_conditions.append(x['when']) new_role = None if 'role_name' in x: diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index 849a1b26ab..df496e11a8 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -1,4 +1,4 @@ -# (c) 2012-2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # @@ -24,7 +24,7 @@ import sys class Task(object): __slots__ = [ - 'name', 'meta', 'action', 'only_if', 'when', 'async_seconds', 'async_poll_interval', + 'name', 'meta', 'action', 'when', 'async_seconds', 'async_poll_interval', 'notify', 'module_name', 'module_args', 'module_vars', 'default_vars', 'play', 'notified_by', 'tags', 'register', 'role_name', 'delegate_to', 'first_available_file', 'ignore_errors', @@ -35,7 +35,7 @@ class Task(object): # to prevent typos and such VALID_KEYS = [ - 'name', 'meta', 'action', 'only_if', 'async', 'poll', 'notify', + 'name', 'meta', 'action', 'when', 'async', 'poll', 'notify', 'first_available_file', 'include', 'tags', 'register', 'ignore_errors', 'delegate_to', 'local_action', 'transport', 'remote_user', 'sudo', 'sudo_user', 'sudo_pass', 'when', 'connection', 'environment', 'args', @@ -96,9 +96,8 @@ class Task(object): elif x in [ 'changed_when', 'failed_when', 'when']: if isinstance(ds[x], basestring) and ds[x].lstrip().startswith("{{"): utils.warning("It is unneccessary to use '{{' in conditionals, leave variables in loop expressions bare.") - ds[x] = "jinja2_compare %s" % (ds[x]) elif x.startswith("when_"): - utils.deprecated("The 'when_' conditional is a deprecated syntax as of 1.2. Switch to using the regular unified 'when' statements as described in ansibleworks.com/docs/.","1.5") + utils.deprecated("The 'when_' conditional has been removed. Switch to using the regular unified 'when' statements as described in ansibleworks.com/docs/.","1.5", removed=True) if 'when' in ds: raise errors.AnsibleError("multiple when_* statements specified in task %s" % (ds.get('name', ds['action']))) @@ -128,8 +127,8 @@ class Task(object): self.module_vars['delay'] = ds.get('delay', 5) self.module_vars['retries'] = ds.get('retries', 3) self.module_vars['register'] = ds.get('register', None) - self.until = "jinja2_compare %s" % (ds.get('until')) - self.module_vars['until'] = utils.compile_when_to_only_if(self.until) + self.until = ds.get('until') + self.module_vars['until'] = self.until # rather than simple key=value args on the options line, these represent structured data and the values # can be hashes and lists, not just scalars @@ -188,22 +187,10 @@ class Task(object): self.name = self.action # load various attributes - self.only_if = ds.get('only_if', 'True') - - if self.only_if != 'True': - utils.deprecated("only_if is a very old feature and has been obsolete since 0.9, please switch to the 'when' conditional as described at http://ansibleworks.com/docs","1.5") - self.when = ds.get('when', None) self.changed_when = ds.get('changed_when', None) - - if self.changed_when is not None: - self.changed_when = utils.compile_when_to_only_if(self.changed_when) - self.failed_when = ds.get('failed_when', None) - if self.failed_when is not None: - self.failed_when = utils.compile_when_to_only_if(self.failed_when) - self.async_seconds = int(ds.get('async', 0)) # not async by default self.async_poll_interval = int(ds.get('poll', 10)) # default poll = 10 seconds self.notify = ds.get('notify', []) @@ -276,12 +263,7 @@ class Task(object): self.tags.extend(apply_tags) self.tags.extend(import_tags) - if self.when is not None: - if self.only_if != 'True': - raise errors.AnsibleError('when obsoletes only_if, only use one or the other') - self.only_if = utils.compile_when_to_only_if(self.when) - if additional_conditions: new_conditions = additional_conditions - new_conditions.append(self.only_if) - self.only_if = new_conditions + new_conditions.append(self.when) + self.when = new_conditions diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 3318bcba2b..ef8d4aac21 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -1,4 +1,4 @@ -# (c) 2012-2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # @@ -38,6 +38,7 @@ import ansible.inventory from ansible import utils from ansible.utils import template from ansible.utils import check_conditional +from ansible.utils import string_functions from ansible import errors from ansible import module_common import poller @@ -308,7 +309,9 @@ class Runner(object): if (module_style != 'new' or async_jid is not None - or not conn.has_pipelining): + or not conn.has_pipelining + or not C.ANSIBLE_SSH_PIPELINING + or C.DEFAULT_KEEP_REMOTE_FILES): self._transfer_str(conn, tmp, module_name, module_data) environment_string = self._compute_environment_string(inject) @@ -351,7 +354,7 @@ class Runner(object): cmd = " ".join([str(x) for x in [remote_module_path, async_jid, async_limit, async_module, argsfile]]) else: if async_jid is None: - if conn.has_pipelining: + if conn.has_pipelining and C.ANSIBLE_SSH_PIPELINING and not C.DEFAULT_KEEP_REMOTE_FILES: in_data = module_data else: cmd = "%s" % (remote_module_path) @@ -405,8 +408,13 @@ class Runner(object): return flags try: - if not new_stdin: - self._new_stdin = os.fdopen(os.dup(sys.stdin.fileno())) + fileno = sys.stdin.fileno() + except ValueError: + fileno = None + + try: + if not new_stdin and fileno is not None: + self._new_stdin = os.fdopen(os.dup(fileno)) else: self._new_stdin = new_stdin @@ -434,7 +442,7 @@ class Runner(object): host_variables = self.inventory.get_variables(host) host_connection = host_variables.get('ansible_connection', self.transport) - if host_connection in [ 'paramiko', 'ssh', 'ssh_alt', 'accelerate' ]: + if host_connection in [ 'paramiko', 'paramiko_alt', 'ssh', 'ssh_old', 'accelerate' ]: port = host_variables.get('ansible_ssh_port', self.remote_port) if port is None: port = C.DEFAULT_REMOTE_PORT @@ -621,7 +629,7 @@ class Runner(object): if not self.accelerate_port: self.accelerate_port = C.ACCELERATE_PORT - if actual_transport in [ 'paramiko', 'ssh', 'ssh_alt', 'accelerate' ]: + if actual_transport in [ 'paramiko', 'paramiko_alt', 'ssh', 'ssh_old', 'accelerate' ]: actual_port = inject.get('ansible_ssh_port', port) # the delegated host may have different SSH port configured, etc @@ -737,6 +745,13 @@ class Runner(object): self.callbacks.on_unreachable(host, result.result) else: data = result.result + + # https://github.com/ansible/ansible/issues/4958 + if hasattr(sys.stdout, "isatty"): + if "stdout" in data and sys.stdout.isatty(): + if not string_functions.isprintable(data['stdout']): + data['stdout'] = '' + if 'item' in inject: result.result['item'] = inject['item'] @@ -783,8 +798,9 @@ class Runner(object): if tmp.find("tmp") != -1: # tmp has already been created return False - if not conn.has_pipelining: + if not conn.has_pipelining or not C.ANSIBLE_SSH_PIPELINING or C.DEFAULT_KEEP_REMOTE_FILES: # tmp is necessary to store the module source code + # or we want to keep the files on the target system return True if module_style != "new": # even when conn has pipelining, old style modules need tmp to store arguments @@ -863,7 +879,7 @@ class Runner(object): def _make_tmp_path(self, conn): ''' make and return a temporary path on a remote box ''' - basefile = 'ansible-%s-%s' % (time.time(), random.randint(0, 2**48)) + basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) basetmp = os.path.join(C.DEFAULT_REMOTE_TMP, basefile) if self.sudo and self.sudo_user != 'root' and basetmp.startswith('$HOME'): basetmp = os.path.join('/tmp', basefile) @@ -879,7 +895,7 @@ class Runner(object): if result['rc'] != 0: if result['rc'] == 5: output = 'Authentication failure.' - elif result['rc'] == 255 and self.transport in ['ssh', 'ssh_alt']: + elif result['rc'] == 255 and self.transport in ['ssh', 'ssh_old']: if utils.VERBOSITY > 3: output = 'SSH encountered an unknown error. The output was:\n%s' % (result['stdout']+result['stderr']) else: @@ -1062,6 +1078,6 @@ class Runner(object): if self.always_run is None: self.always_run = self.module_vars.get('always_run', False) self.always_run = check_conditional( - self.always_run, self.basedir, inject, fail_on_undefined=True, jinja2=True) + self.always_run, self.basedir, inject, fail_on_undefined=True) return (self.check and not self.always_run) diff --git a/lib/ansible/runner/action_plugins/assemble.py b/lib/ansible/runner/action_plugins/assemble.py index 3f55023aa9..773f77e6b1 100644 --- a/lib/ansible/runner/action_plugins/assemble.py +++ b/lib/ansible/runner/action_plugins/assemble.py @@ -1,4 +1,4 @@ -# (c) 2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2013-2014, Michael DeHaan <michael.dehaan@gmail.com> # Stephen Fromm <sfromm@gmail.com> # Brian Coca <briancoca+dev@gmail.com> # diff --git a/lib/ansible/runner/action_plugins/async.py b/lib/ansible/runner/action_plugins/async.py index c977af007c..12fe279a47 100644 --- a/lib/ansible/runner/action_plugins/async.py +++ b/lib/ansible/runner/action_plugins/async.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/runner/action_plugins/copy.py b/lib/ansible/runner/action_plugins/copy.py index 7364de9b10..6cff391d50 100644 --- a/lib/ansible/runner/action_plugins/copy.py +++ b/lib/ansible/runner/action_plugins/copy.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # @@ -35,8 +35,6 @@ sys.setdefaultencoding("utf8") class ActionModule(object): - TRANSFERS_FILES = True - def __init__(self, runner): self.runner = runner @@ -125,9 +123,6 @@ class ActionModule(object): changed = False diffs = [] module_result = {"changed": False} - # Remove tmp path since a new one is created below. Should be empty. - if tmp.find("tmp") != -1: - self.runner._low_level_exec_command(conn, "rm -rf %s > /dev/null 2>&1" % tmp, tmp) for source_full, source_rel in source_files: # We need to get a new tmp path for each file, otherwise the copy module deletes the folder. tmp = self.runner._make_tmp_path(conn) diff --git a/lib/ansible/runner/action_plugins/fetch.py b/lib/ansible/runner/action_plugins/fetch.py index 28e06f8cfc..205023fad9 100644 --- a/lib/ansible/runner/action_plugins/fetch.py +++ b/lib/ansible/runner/action_plugins/fetch.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/runner/action_plugins/include_vars.py b/lib/ansible/runner/action_plugins/include_vars.py index 344cc82fa6..8d333af6df 100644 --- a/lib/ansible/runner/action_plugins/include_vars.py +++ b/lib/ansible/runner/action_plugins/include_vars.py @@ -1,4 +1,4 @@ -# (c) 2013, Benno Joy <benno@ansibleworks.com> +# (c) 2013-2014, Benno Joy <benno@ansibleworks.com> # # This file is part of Ansible # diff --git a/lib/ansible/runner/action_plugins/synchronize.py b/lib/ansible/runner/action_plugins/synchronize.py index 0d7fdf074e..af9102138c 100644 --- a/lib/ansible/runner/action_plugins/synchronize.py +++ b/lib/ansible/runner/action_plugins/synchronize.py @@ -34,13 +34,27 @@ class ActionModule(object): else: return path + def _process_remote(self, host, path, user): + transport = self.runner.transport + if not host in ['127.0.0.1', 'localhost'] or transport != "local": + return '%s@%s:%s' % (user, host, path) + else: + return path + def setup(self, module_name, inject): ''' Always default to localhost as delegate if None defined ''' + + # Store original transport and sudo values. + self.original_transport = inject.get('ansible_connection', self.runner.transport) + self.original_sudo = self.runner.sudo + self.transport_overridden = False + if inject.get('delegate_to') is None: inject['delegate_to'] = '127.0.0.1' - inject['ansible_connection'] = 'local' - # If sudo is active, disable from the connection set self.sudo to True. - if self.runner.sudo: + # IF original transport is not local, override transport and disable sudo. + if self.original_transport != 'local': + inject['ansible_connection'] = 'local' + self.transport_overridden = True self.runner.sudo = False def run(self, conn, tmp, module_name, module_args, @@ -69,12 +83,15 @@ class ActionModule(object): # from the perspective of the rsync call the delegate is the localhost src_host = '127.0.0.1' dest_host = inject.get('ansible_ssh_host', inject['inventory_hostname']) + # allow ansible_ssh_host to be templated + dest_host = template.template(self.runner.basedir, dest_host, inject, fail_on_undefined=True) + dest_is_local = dest_host in ['127.0.0.1', 'localhost'] dest_port = options.get('dest_port') inv_port = inject.get('ansible_ssh_port', inject['inventory_hostname']) if inv_port != dest_port and inv_port != inject['inventory_hostname']: options['dest_port'] = inv_port - + # edge case: explicit delegate and dest_host are the same if dest_host == inject['delegate_to']: @@ -82,7 +99,7 @@ class ActionModule(object): if options.get('mode', 'push') == 'pull': (dest_host, src_host) = (src_host, dest_host) - if not dest_host is src_host: + if not dest_host is src_host and self.original_transport != 'local': user = inject.get('ansible_ssh_user', self.runner.remote_user) private_key = inject.get('ansible_ssh_private_key_file', self.runner.private_key_file) @@ -90,16 +107,26 @@ class ActionModule(object): private_key = os.path.expanduser(private_key) options['private_key'] = private_key - src = self._process_origin(src_host, src, user) - dest = self._process_origin(dest_host, dest, user) + # use the mode to define src and dest's url + if options.get('mode', 'push') == 'pull': + # src is a remote path: <user>@<host>, dest is a local path + src = self._process_remote(src_host, src, user) + dest = self._process_origin(dest_host, dest, user) + else: + # src is a local path, dest is a remote path: <user>@<host> + src = self._process_origin(src_host, src, user) + dest = self._process_remote(dest_host, dest, user) options['src'] = src options['dest'] = dest if 'mode' in options: del options['mode'] + # Allow custom rsync path argument. rsync_path = options.get('rsync_path', None) - if not rsync_path and self.runner.sudo: + + # If no rsync_path is set, sudo was originally set, and dest is remote then add 'sudo rsync' argument. + if not rsync_path and self.transport_overridden and self.original_sudo and not dest_is_local: rsync_path = 'sudo rsync' # make sure rsync path is quoted. diff --git a/lib/ansible/runner/connection_plugins/paramiko_alt.py b/lib/ansible/runner/connection_plugins/paramiko_alt.py new file mode 100644 index 0000000000..fbba732631 --- /dev/null +++ b/lib/ansible/runner/connection_plugins/paramiko_alt.py @@ -0,0 +1,341 @@ +# (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/>. + + +# --- +# The paramiko transport is provided because many distributions, in particular EL6 and before +# do not support ControlPersist in their SSH implementations. This is needed on the Ansible +# control machine to be reasonably efficient with connections. Thus paramiko is faster +# for most users on these platforms. Users with ControlPersist capability can consider +# using -c ssh or configuring the transport in ansible.cfg. + +import warnings +import os +import pipes +import socket +import random +import logging +import traceback +import fcntl +import sys +from termios import tcflush, TCIFLUSH +from binascii import hexlify +from ansible.callbacks import vvv +from ansible import errors +from ansible import utils +from ansible import constants as C + +AUTHENTICITY_MSG=""" +paramiko: The authenticity of host '%s' can't be established. +The %s key fingerprint is %s. +Are you sure you want to continue connecting (yes/no)? +""" + +# prevent paramiko warning noise -- see http://stackoverflow.com/questions/3920502/ +HAVE_PARAMIKO=False +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + try: + import paramiko + HAVE_PARAMIKO=True + logging.getLogger("paramiko").setLevel(logging.WARNING) + except ImportError: + pass + +class MyAddPolicy(object): + """ + Based on AutoAddPolicy in paramiko so we can determine when keys are added + and also prompt for input. + + Policy for automatically adding the hostname and new host key to the + local L{HostKeys} object, and saving it. This is used by L{SSHClient}. + """ + + def __init__(self, runner): + self.runner = runner + + def missing_host_key(self, client, hostname, key): + + if C.HOST_KEY_CHECKING: + + fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) + fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX) + + old_stdin = sys.stdin + sys.stdin = self.runner._new_stdin + fingerprint = hexlify(key.get_fingerprint()) + ktype = key.get_name() + + # clear out any premature input on sys.stdin + tcflush(sys.stdin, TCIFLUSH) + + inp = raw_input(AUTHENTICITY_MSG % (hostname, ktype, fingerprint)) + sys.stdin = old_stdin + if inp not in ['yes','y','']: + fcntl.flock(self.runner.output_lockfile, fcntl.LOCK_UN) + fcntl.flock(self.runner.process_lockfile, fcntl.LOCK_UN) + raise errors.AnsibleError("host connection rejected by user") + + fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_UN) + fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN) + + + key._added_by_ansible_this_time = True + + # existing implementation below: + client._host_keys.add(hostname, key.get_name(), key) + + # host keys are actually saved in close() function below + # in order to control ordering. + + +# keep connection objects on a per host basis to avoid repeated attempts to reconnect + +SSH_CONNECTION_CACHE = {} +SFTP_CONNECTION_CACHE = {} + +class Connection(object): + ''' SSH based connections with Paramiko ''' + + def __init__(self, runner, host, port, user, password, private_key_file, *args, **kwargs): + + self.ssh = None + self.sftp = None + self.runner = runner + self.host = host + self.port = port + self.user = user + self.password = password + self.private_key_file = private_key_file + self.has_pipelining = True + + def _cache_key(self): + return "%s__%s__" % (self.host, self.user) + + def connect(self): + cache_key = self._cache_key() + if cache_key in SSH_CONNECTION_CACHE: + self.ssh = SSH_CONNECTION_CACHE[cache_key] + else: + self.ssh = SSH_CONNECTION_CACHE[cache_key] = self._connect_uncached() + return self + + def _connect_uncached(self): + ''' activates the connection object ''' + + if not HAVE_PARAMIKO: + raise errors.AnsibleError("paramiko is not installed") + + vvv("ESTABLISH CONNECTION FOR USER: %s on PORT %s TO %s" % (self.user, self.port, self.host), host=self.host) + + ssh = paramiko.SSHClient() + + self.keyfile = os.path.expanduser("~/.ssh/known_hosts") + + if C.HOST_KEY_CHECKING: + ssh.load_system_host_keys() + ssh.set_missing_host_key_policy(MyAddPolicy(self.runner)) + + allow_agent = True + if self.password is not None: + allow_agent = False + try: + if self.private_key_file: + key_filename = os.path.expanduser(self.private_key_file) + elif self.runner.private_key_file: + key_filename = os.path.expanduser(self.runner.private_key_file) + else: + key_filename = None + ssh.connect(self.host, username=self.user, allow_agent=allow_agent, look_for_keys=True, + key_filename=key_filename, password=self.password, + timeout=self.runner.timeout, port=self.port) + except Exception, e: + msg = str(e) + if "PID check failed" in msg: + raise errors.AnsibleError("paramiko version issue, please upgrade paramiko on the machine running ansible") + elif "Private key file is encrypted" in msg: + msg = 'ssh %s@%s:%s : %s\nTo connect as a different user, use -u <username>.' % ( + self.user, self.host, self.port, msg) + raise errors.AnsibleConnectionFailed(msg) + else: + raise errors.AnsibleConnectionFailed(msg) + + return ssh + + def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh', in_data=None): + ''' run a command on the remote host ''' + + bufsize = 4096 + try: + chan = self.ssh.get_transport().open_session() + except Exception, e: + msg = "Failed to open session" + if len(str(e)) > 0: + msg += ": %s" % str(e) + raise errors.AnsibleConnectionFailed(msg) + + if not self.runner.sudo or not sudoable or in_data: + if executable: + quoted_command = executable + ' -c ' + pipes.quote(cmd) + else: + quoted_command = cmd + vvv("EXEC ALT no-tty %s" % quoted_command, host=self.host) + chan.exec_command(quoted_command) + else: + # sudo usually requires a PTY (cf. requiretty option), therefore + # we give it one by default (pty=True in ansble.cfg), and we try + # to initialise from the calling environment + if C.PARAMIKO_PTY: + chan.get_pty(term=os.getenv('TERM', 'vt100'), + width=int(os.getenv('COLUMNS', 0)), + height=int(os.getenv('LINES', 0))) + shcmd, prompt, success_key = utils.make_sudo_cmd(sudo_user, executable, cmd) + vvv("EXEC %s" % shcmd, host=self.host) + sudo_output = '' + try: + chan.exec_command(shcmd) + if self.runner.sudo_pass: + while not sudo_output.endswith(prompt) and success_key not in sudo_output: + chunk = chan.recv(bufsize) + if not chunk: + if 'unknown user' in sudo_output: + raise errors.AnsibleError( + 'user %s does not exist' % sudo_user) + else: + raise errors.AnsibleError('ssh connection ' + + 'closed waiting for password prompt') + sudo_output += chunk + if success_key not in sudo_output: + chan.sendall(self.runner.sudo_pass + '\n') + except socket.timeout: + raise errors.AnsibleError('ssh timed out waiting for sudo.\n' + sudo_output) + + if in_data: + try: + stdin = chan.makefile('wb') + stdin.write(in_data) + chan.shutdown_write() + except Exception, e: + raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh.') + + stdout = ''.join(chan.makefile('rb', bufsize)) + stderr = ''.join(chan.makefile_stderr('rb', bufsize)) + + return (chan.recv_exit_status(), '', stdout, stderr) + + def put_file(self, in_path, out_path): + ''' transfer a file from local to remote ''' + vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) + if not os.path.exists(in_path): + raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) + try: + self.sftp = self.ssh.open_sftp() + except Exception, e: + raise errors.AnsibleError("failed to open a SFTP connection (%s)" % e) + try: + self.sftp.put(in_path, out_path) + except IOError: + raise errors.AnsibleError("failed to transfer file to %s" % out_path) + + def _connect_sftp(self): + cache_key = "%s__%s__" % (self.host, self.user) + if cache_key in SFTP_CONNECTION_CACHE: + return SFTP_CONNECTION_CACHE[cache_key] + else: + result = SFTP_CONNECTION_CACHE[cache_key] = self.connect().ssh.open_sftp() + return result + + def fetch_file(self, in_path, out_path): + ''' save a remote file to the specified path ''' + vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) + try: + self.sftp = self._connect_sftp() + except Exception, e: + raise errors.AnsibleError("failed to open a SFTP connection (%s)", e) + try: + self.sftp.get(in_path, out_path) + except IOError: + raise errors.AnsibleError("failed to transfer file from %s" % in_path) + + def _any_keys_added(self): + added_any = False + for hostname, keys in self.ssh._host_keys.iteritems(): + for keytype, key in keys.iteritems(): + added_this_time = getattr(key, '_added_by_ansible_this_time', False) + if added_this_time: + return True + return False + + def _save_ssh_host_keys(self, filename): + ''' + not using the paramiko save_ssh_host_keys function as we want to add new SSH keys at the bottom so folks + don't complain about it :) + ''' + + if not self._any_keys_added(): + return False + + path = os.path.expanduser("~/.ssh") + if not os.path.exists(path): + os.makedirs(path) + + f = open(filename, 'w') + for hostname, keys in self.ssh._host_keys.iteritems(): + for keytype, key in keys.iteritems(): + # was f.write + added_this_time = getattr(key, '_added_by_ansible_this_time', False) + if not added_this_time: + f.write("%s %s %s\n" % (hostname, keytype, key.get_base64())) + for hostname, keys in self.ssh._host_keys.iteritems(): + for keytype, key in keys.iteritems(): + added_this_time = getattr(key, '_added_by_ansible_this_time', False) + if added_this_time: + f.write("%s %s %s\n" % (hostname, keytype, key.get_base64())) + f.close() + + def close(self): + ''' terminate the connection ''' + cache_key = self._cache_key() + SSH_CONNECTION_CACHE.pop(cache_key, None) + SFTP_CONNECTION_CACHE.pop(cache_key, None) + if self.sftp is not None: + self.sftp.close() + + if C.PARAMIKO_RECORD_HOST_KEYS and self._any_keys_added(): + + # add any new SSH host keys -- warning -- this could be slow + lockfile = self.keyfile.replace("known_hosts",".known_hosts.lock") + dirname = os.path.dirname(self.keyfile) + if not os.path.exists(dirname): + os.makedirs(dirname) + + KEY_LOCK = open(lockfile, 'w') + fcntl.lockf(KEY_LOCK, fcntl.LOCK_EX) + try: + # just in case any were added recently + self.ssh.load_system_host_keys() + self.ssh._host_keys.update(self.ssh._system_host_keys) + self._save_ssh_host_keys(self.keyfile) + except: + # unable to save keys, including scenario when key was invalid + # and caught earlier + traceback.print_exc() + pass + fcntl.lockf(KEY_LOCK, fcntl.LOCK_UN) + + self.ssh.close() + diff --git a/lib/ansible/runner/connection_plugins/ssh.py b/lib/ansible/runner/connection_plugins/ssh.py index 3da15de675..d987d609a8 100644 --- a/lib/ansible/runner/connection_plugins/ssh.py +++ b/lib/ansible/runner/connection_plugins/ssh.py @@ -45,7 +45,7 @@ class Connection(object): self.password = password self.private_key_file = private_key_file self.HASHED_KEY_MAGIC = "|1|" - self.has_pipelining = False + self.has_pipelining = True fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) self.cp_dir = utils.prepare_writeable_dir('$HOME/.ansible/cp',mode=0700) @@ -148,11 +148,13 @@ class Connection(object): def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False, executable='/bin/sh', in_data=None): ''' run a command on the remote host ''' - if in_data: - raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining") - ssh_cmd = self._password_cmd() - ssh_cmd += ["ssh", "-tt"] + ssh_cmd += ["ssh", "-C"] + if not in_data: + # we can only use tty when we are not pipelining the modules. piping data into /usr/bin/python + # inside a tty automatically invokes the python interactive-mode but the modules are not + # compatible with the interactive-mode ("unexpected indent" mainly because of empty lines) + ssh_cmd += ["-tt"] if utils.VERBOSITY > 3: ssh_cmd += ["-vvv"] else: @@ -182,45 +184,81 @@ class Connection(object): fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX) - - - try: - # Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors - master, slave = pty.openpty() - p = subprocess.Popen(ssh_cmd, stdin=slave, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdin = os.fdopen(master, 'w', 0) - os.close(slave) - except: + # create process + if in_data: + # do not use pseudo-pty p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdin = p.stdin + else: + # try to use upseudo-pty + try: + # Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors + master, slave = pty.openpty() + p = subprocess.Popen(ssh_cmd, stdin=slave, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdin = os.fdopen(master, 'w', 0) + os.close(slave) + except: + p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdin = p.stdin self._send_password() if self.runner.sudo and sudoable and self.runner.sudo_pass: + # several cases are handled for sudo privileges with password + # * NOPASSWD (tty & no-tty): detect success_key on stdout + # * without NOPASSWD: + # * detect prompt on stdout (tty) + # * detect prompt on stderr (no-tty) fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK) + fcntl.fcntl(p.stderr, fcntl.F_SETFL, + fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK) sudo_output = '' + sudo_errput = '' + while not sudo_output.endswith(prompt) and success_key not in sudo_output: - rfd, wfd, efd = select.select([p.stdout], [], + rfd, wfd, efd = select.select([p.stdout, p.stderr], [], [p.stdout], self.runner.timeout) + if p.stderr in rfd: + chunk = p.stderr.read() + if not chunk: + raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt') + sudo_errput += chunk + incorrect_password = gettext.dgettext( + "sudo", "Sorry, try again.") + if sudo_errput.strip().endswith("%s%s" % (prompt, incorrect_password)): + raise errors.AnsibleError('Incorrect sudo password') + elif sudo_errput.endswith(prompt): + stdin.write(self.runner.sudo_pass + '\n') + if p.stdout in rfd: chunk = p.stdout.read() if not chunk: raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt') sudo_output += chunk - else: + + if not rfd: + # timeout. wrap up process communication stdout = p.communicate() raise errors.AnsibleError('ssh connection error waiting for sudo password prompt') + if success_key not in sudo_output: stdin.write(self.runner.sudo_pass + '\n') fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK) - + fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK) # We can't use p.communicate here because the ControlMaster may have stdout open as well stdout = '' stderr = '' rpipes = [p.stdout, p.stderr] + if in_data: + try: + stdin.write(in_data) + stdin.close() + except: + raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh') while True: rfd, wfd, efd = select.select(rpipes, [], rpipes, 1) @@ -256,8 +294,15 @@ class Connection(object): fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_UN) fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN) controlpersisterror = stderr.find('Bad configuration option: ControlPersist') != -1 or stderr.find('unknown configuration option: ControlPersist') != -1 + + if C.HOST_KEY_CHECKING: + if ssh_cmd[0] == "sshpass" and p.returncode == 6: + raise errors.AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host\'s fingerprint to your known_hosts file to manage this host.') + if p.returncode != 0 and controlpersisterror: raise errors.AnsibleError('using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS="" (or ansible_ssh_args in the config file) before running again') + if p.returncode == 255 and in_data: + raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh') return (p.returncode, '', stdout, stderr) diff --git a/lib/ansible/runner/connection_plugins/ssh_alt.py b/lib/ansible/runner/connection_plugins/ssh_old.py similarity index 89% rename from lib/ansible/runner/connection_plugins/ssh_alt.py rename to lib/ansible/runner/connection_plugins/ssh_old.py index 7b40cb7e4c..ea0857d406 100644 --- a/lib/ansible/runner/connection_plugins/ssh_alt.py +++ b/lib/ansible/runner/connection_plugins/ssh_old.py @@ -45,7 +45,7 @@ class Connection(object): self.password = password self.private_key_file = private_key_file self.HASHED_KEY_MAGIC = "|1|" - self.has_pipelining = True + self.has_pipelining = False fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) self.cp_dir = utils.prepare_writeable_dir('$HOME/.ansible/cp',mode=0700) @@ -148,10 +148,11 @@ class Connection(object): def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False, executable='/bin/sh', in_data=None): ''' run a command on the remote host ''' + if in_data: + raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining") + ssh_cmd = self._password_cmd() - ssh_cmd += ["ssh", "-C"] - if not in_data: - ssh_cmd += ["-tt"] + ssh_cmd += ["ssh", "-tt"] if utils.VERBOSITY > 3: ssh_cmd += ["-vvv"] else: @@ -181,26 +182,19 @@ class Connection(object): fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX) fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX) - # create process - if in_data: - # do not use pseudo-pty + + + try: + # Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors + master, slave = pty.openpty() + p = subprocess.Popen(ssh_cmd, stdin=slave, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdin = os.fdopen(master, 'w', 0) + os.close(slave) + except: p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdin = p.stdin - else: - # try to use upseudo-pty - try: - # Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors - master, slave = pty.openpty() - p = subprocess.Popen(ssh_cmd, stdin=slave, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdin = os.fdopen(master, 'w', 0) - os.close(slave) - except: - p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdin = p.stdin - self._send_password() @@ -208,9 +202,6 @@ class Connection(object): fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK) sudo_output = '' - if in_data: - # no terminal => no prompt on output. process is waiting for sudo_pass - stdin.write(self.runner.sudo_pass + '\n') while not sudo_output.endswith(prompt) and success_key not in sudo_output: rfd, wfd, efd = select.select([p.stdout], [], [p.stdout], self.runner.timeout) @@ -225,16 +216,11 @@ class Connection(object): if success_key not in sudo_output: stdin.write(self.runner.sudo_pass + '\n') fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK) + # We can't use p.communicate here because the ControlMaster may have stdout open as well stdout = '' stderr = '' rpipes = [p.stdout, p.stderr] - if in_data: - try: - stdin.write(in_data) - stdin.close() - except: - raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh') while True: rfd, wfd, efd = select.select(rpipes, [], rpipes, 1) @@ -269,11 +255,14 @@ class Connection(object): # the host to known hosts is not intermingled with multiprocess output. fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_UN) fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN) + + if C.HOST_KEY_CHECKING: + if ssh_cmd[0] == "sshpass" and p.returncode == 6: + raise errors.AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host\'s fingerprint to your known_hosts file to manage this host.') + controlpersisterror = stderr.find('Bad configuration option: ControlPersist') != -1 or stderr.find('unknown configuration option: ControlPersist') != -1 if p.returncode != 0 and controlpersisterror: raise errors.AnsibleError('using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS="" (or ansible_ssh_args in the config file) before running again') - if p.returncode == 255 and in_data: - raise errors.AnsibleError('SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh') return (p.returncode, '', stdout, stderr) diff --git a/lib/ansible/runner/filter_plugins/core.py b/lib/ansible/runner/filter_plugins/core.py index 495e17e2fa..1cd53bbaa3 100644 --- a/lib/ansible/runner/filter_plugins/core.py +++ b/lib/ansible/runner/filter_plugins/core.py @@ -58,7 +58,7 @@ def changed(*a, **kw): if not 'changed' in item: changed = False if ('results' in item # some modules return a 'results' key - and type(item['results']) == list + and type(item['results']) == list and type(item['results'][0]) == dict): for result in item['results']: changed = changed or result.get('changed', False) @@ -76,9 +76,12 @@ def skipped(*a, **kw): def mandatory(a): ''' Make a variable mandatory ''' - if not a: + try: + a + except NameError: raise errors.AnsibleFilterError('Mandatory variable not defined.') - return a + else: + return a def bool(a): ''' return a bool for the arg ''' diff --git a/lib/ansible/runner/lookup_plugins/csvfile.py b/lib/ansible/runner/lookup_plugins/csvfile.py new file mode 100644 index 0000000000..7c6f376bf2 --- /dev/null +++ b/lib/ansible/runner/lookup_plugins/csvfile.py @@ -0,0 +1,82 @@ +# (c) 2013, Jan-Piet Mens <jpmens(at)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/>. + +from ansible import utils, errors +import os +import codecs +import csv + +class LookupModule(object): + + def __init__(self, basedir=None, **kwargs): + self.basedir = basedir + + def read_csv(self, filename, key, delimiter, dflt=None, col=1): + + try: + f = codecs.open(filename, 'r', encoding='utf-8') + creader = csv.reader(f, delimiter=delimiter) + + for row in creader: + if row[0] == key: + return row[int(col)] + except Exception, e: + raise errors.AnsibleError("csvfile: %s" % str(e)) + + return dflt + + def run(self, terms, inject=None, **kwargs): + + terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject) + + if isinstance(terms, basestring): + terms = [ terms ] + + ret = [] + for term in terms: + params = term.split() + key = params[0] + + paramvals = { + 'file' : 'ansible.csv', + 'default' : None, + 'delimiter' : "TAB", + 'col' : "1", # column to return + } + + # parameters specified? + try: + for param in params[1:]: + name, value = param.split('=') + assert(name in paramvals) + paramvals[name] = value + except (ValueError, AssertionError) as e: + raise errors.AnsibleError(e) + + if paramvals['delimiter'] == 'TAB': + paramvals['delimiter'] = "\t" + + path = utils.path_dwim(self.basedir, paramvals['file']) + + var = self.read_csv(path, key, paramvals['delimiter'], paramvals['default'], paramvals['col']) + if var is not None: + if type(var) is list: + for v in var: + ret.append(v) + else: + ret.append(var) + return ret diff --git a/lib/ansible/runner/lookup_plugins/etcd.py b/lib/ansible/runner/lookup_plugins/etcd.py index 04508cdfff..07adec8029 100644 --- a/lib/ansible/runner/lookup_plugins/etcd.py +++ b/lib/ansible/runner/lookup_plugins/etcd.py @@ -15,8 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. -from ansible import utils, errors, constants -import os +from ansible import utils import urllib2 try: import json @@ -27,7 +26,7 @@ except ImportError: ANSIBLE_ETCD_URL = 'http://127.0.0.1:4001' class etcd(): - def __init__(self, url=ANSIBLE_ETCD_URL) + def __init__(self, url=ANSIBLE_ETCD_URL): self.url = url self.baseurl = '%s/v1/keys' % (self.url) diff --git a/lib/ansible/runner/poller.py b/lib/ansible/runner/poller.py index af2be9246f..c69b2e76da 100644 --- a/lib/ansible/runner/poller.py +++ b/lib/ansible/runner/poller.py @@ -1,4 +1,4 @@ -# (c) 2012-2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/runner/return_data.py b/lib/ansible/runner/return_data.py index 5e4933fefb..94e454cc98 100644 --- a/lib/ansible/runner/return_data.py +++ b/lib/ansible/runner/return_data.py @@ -1,4 +1,4 @@ -# (c) 2012-2013, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index 126e86869a..a263ed15ff 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -163,58 +163,47 @@ def is_changed(result): return (result.get('changed', False) in [ True, 'True', 'true']) -def check_conditional(conditional, basedir, inject, fail_on_undefined=False, jinja2=False): +def check_conditional(conditional, basedir, inject, fail_on_undefined=False): + + if conditional is None or conditional == '': + return True if isinstance(conditional, list): for x in conditional: - if not check_conditional(x, basedir, inject, fail_on_undefined=fail_on_undefined, jinja2=jinja2): + if not check_conditional(x, basedir, inject, fail_on_undefined=fail_on_undefined): return False return True - if jinja2: - conditional = "jinja2_compare %s" % conditional - - if conditional.startswith("jinja2_compare"): - conditional = conditional.replace("jinja2_compare ","") - # allow variable names - if conditional in inject and str(inject[conditional]).find('-') == -1: - conditional = inject[conditional] - conditional = template.template(basedir, conditional, inject, fail_on_undefined=fail_on_undefined) - original = str(conditional).replace("jinja2_compare ","") - # a Jinja2 evaluation that results in something Python can eval! - presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional - conditional = template.template(basedir, presented, inject) - val = conditional.strip() - if val == presented: - # the templating failed, meaning most likely a - # variable was undefined. If we happened to be - # looking for an undefined variable, return True, - # otherwise fail - if conditional.find("is undefined") != -1: - return True - elif conditional.find("is defined") != -1: - return False - else: - raise errors.AnsibleError("error while evaluating conditional: %s" % original) - elif val == "True": - return True - elif val == "False": - return False - else: - raise errors.AnsibleError("unable to evaluate conditional: %s" % original) - if not isinstance(conditional, basestring): return conditional - try: - conditional = conditional.replace("\n", "\\n") - result = safe_eval(conditional) - if result not in [ True, False ]: - raise errors.AnsibleError("Conditional expression must evaluate to True or False: %s" % conditional) - return result - - except (NameError, SyntaxError): - raise errors.AnsibleError("Could not evaluate the expression: (%s)" % conditional) + conditional = conditional.replace("jinja2_compare ","") + # allow variable names + if conditional in inject and str(inject[conditional]).find('-') == -1: + conditional = inject[conditional] + conditional = template.template(basedir, conditional, inject, fail_on_undefined=fail_on_undefined) + original = str(conditional).replace("jinja2_compare ","") + # a Jinja2 evaluation that results in something Python can eval! + presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional + conditional = template.template(basedir, presented, inject) + val = conditional.strip() + if val == presented: + # the templating failed, meaning most likely a + # variable was undefined. If we happened to be + # looking for an undefined variable, return True, + # otherwise fail + if conditional.find("is undefined") != -1: + return True + elif conditional.find("is defined") != -1: + return False + else: + raise errors.AnsibleError("error while evaluating conditional: %s" % original) + elif val == "True": + return True + elif val == "False": + return False + else: + raise errors.AnsibleError("unable to evaluate conditional: %s" % original) def is_executable(path): '''is the given path executable?''' @@ -458,8 +447,30 @@ Note: The error may actually appear before this position: line %s, column %s %s %s""" % (path, mark.line + 1, mark.column + 1, before_probline, probline, arrow) - msg = process_common_errors(msg, probline, mark.column) + unquoted_var = None + if '{{' in probline and '}}' in probline: + if '"{{' not in probline or "'{{" not in probline: + unquoted_var = True + msg = process_common_errors(msg, probline, mark.column) + if not unquoted_var: + msg = process_common_errors(msg, probline, mark.column) + else: + msg = msg + """ +We could be wrong, but this one looks like it might be an issue with +missing quotes. Always quote template expression brackets when they +start a value. For instance: + + with_items: + - {{ foo }} + +Should be written as: + + with_items: + - "{{ foo }}" + +""" + msg = process_common_errors(msg, probline, mark.column) else: # No problem markers means we have to throw a generic @@ -756,86 +767,6 @@ def boolean(value): else: return False -def compile_when_to_only_if(expression): - ''' - when is a shorthand for writing only_if conditionals. It requires less quoting - magic. only_if is retained for backwards compatibility. - ''' - - # when: set $variable - # when: unset $variable - # when: failed $json_result - # when: changed $json_result - # when: int $x >= $z and $y < 3 - # when: int $x in $alist - # when: float $x > 2 and $y <= $z - # when: str $x != $y - # when: jinja2_compare asdf # implies {{ asdf }} - - if type(expression) not in [ str, unicode ]: - raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression) - tokens = expression.split() - if len(tokens) < 2: - raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression) - - # when_set / when_unset - if tokens[0] in [ 'set', 'unset' ]: - tcopy = tokens[1:] - for (i,t) in enumerate(tokens[1:]): - if t.find("$") != -1: - tcopy[i] = "is_%s('''%s''')" % (tokens[0], t) - else: - tcopy[i] = t - return " ".join(tcopy) - - # when_failed / when_changed - elif tokens[0] in [ 'failed', 'changed' ]: - tcopy = tokens[1:] - for (i,t) in enumerate(tokens[1:]): - if t.find("$") != -1: - tcopy[i] = "is_%s(%s)" % (tokens[0], t) - else: - tcopy[i] = t - return " ".join(tcopy) - - # when_integer / when_float / when_string - elif tokens[0] in [ 'integer', 'float', 'string' ]: - cast = None - if tokens[0] == 'integer': - cast = 'int' - elif tokens[0] == 'string': - cast = 'str' - elif tokens[0] == 'float': - cast = 'float' - tcopy = tokens[1:] - for (i,t) in enumerate(tokens[1:]): - #if re.search(t, r"^\w"): - # bare word will turn into Jinja2 so all the above - # casting is really not needed - #tcopy[i] = "%s('''%s''')" % (cast, t) - t2 = t.strip() - if (t2[0].isalpha() or t2[0] == '$') and cast == 'str' and t2 != 'in': - tcopy[i] = "'%s'" % (t) - else: - tcopy[i] = t - result = " ".join(tcopy) - return result - - - # when_boolean - elif tokens[0] in [ 'bool', 'boolean' ]: - tcopy = tokens[1:] - for (i, t) in enumerate(tcopy): - if t.find("$") != -1: - tcopy[i] = "(is_set('''%s''') and '''%s'''.lower() not in ('false', 'no', 'n', 'none', '0', ''))" % (t, t) - return " ".join(tcopy) - - # the stock 'when' without qualification (new in 1.2), assumes Jinja2 terms - elif tokens[0] == 'jinja2_compare': - return " ".join(tokens) - else: - raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression) - def make_sudo_cmd(sudo_user, executable, cmd): """ helper function for connection plugins to create sudo commands @@ -979,12 +910,21 @@ def listify_lookup_plugin_terms(terms, basedir, inject): return terms -def deprecated(msg, version): +def deprecated(msg, version, removed=False): ''' used to print out a deprecation message.''' - if not C.DEPRECATION_WARNINGS: + + if not removed and not C.DEPRECATION_WARNINGS: return - new_msg = "\n[DEPRECATION WARNING]: %s. This feature will be removed in version %s." % (msg, version) - new_msg = new_msg + " Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.\n\n" + + if not removed: + if version: + new_msg = "\n[DEPRECATION WARNING]: %s. This feature will be removed in version %s." % (msg, version) + else: + new_msg = "\n[DEPRECATION WARNING]: %s. This feature will be removed in a future release." % (msg) + new_msg = new_msg + " Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.\n\n" + else: + raise errors.AnsibleError("[DEPRECATED]: %s. Please update your playbooks." % msg) + wrapped = textwrap.wrap(new_msg, 79) new_msg = "\n".join(wrapped) + "\n" diff --git a/lib/ansible/utils/cmd_functions.py b/lib/ansible/utils/cmd_functions.py new file mode 100644 index 0000000000..6525260f10 --- /dev/null +++ b/lib/ansible/utils/cmd_functions.py @@ -0,0 +1,59 @@ +# (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/>. + +import os +import sys +import shlex +import subprocess +import select + +def run_cmd(cmd, live=False, readsize=10): + + #readsize = 10 + + cmdargs = shlex.split(cmd) + p = subprocess.Popen(cmdargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + stdout = '' + stderr = '' + rpipes = [p.stdout, p.stderr] + while True: + rfd, wfd, efd = select.select(rpipes, [], rpipes, 1) + + if p.stdout in rfd: + dat = os.read(p.stdout.fileno(), readsize) + if live: + sys.stdout.write(dat) + stdout += dat + if dat == '': + rpipes.remove(p.stdout) + if p.stderr in rfd: + dat = os.read(p.stderr.fileno(), readsize) + stderr += dat + if live: + sys.stdout.write(dat) + if dat == '': + rpipes.remove(p.stderr) + # only break out if we've emptied the pipes, or there is nothing to + # read from and the process has finished. + if (not rpipes or not rfd) and p.poll() is not None: + break + # Calling wait while there are still pipes to read can cause a lock + elif not rpipes and p.poll() == None: + p.wait() + + return p.returncode, stdout, stderr diff --git a/lib/ansible/utils/module_docs.py b/lib/ansible/utils/module_docs.py index afa4c6536e..3a5d078296 100644 --- a/lib/ansible/utils/module_docs.py +++ b/lib/ansible/utils/module_docs.py @@ -25,7 +25,7 @@ import traceback # modules that are ok that they do not have documentation strings BLACKLIST_MODULES = [ - 'async_wrapper', + 'async_wrapper', 'accelerate', 'async_status' ] def get_docstring(filename, verbose=False): diff --git a/lib/ansible/utils/string_functions.py b/lib/ansible/utils/string_functions.py new file mode 100644 index 0000000000..6d71c7bfa2 --- /dev/null +++ b/lib/ansible/utils/string_functions.py @@ -0,0 +1,7 @@ +def isprintable(instring): + #http://stackoverflow.com/a/3637294 + import string + printset = set(string.printable) + isprintable = set(instring).issubset(printset) + return isprintable + diff --git a/lib/ansible/utils/template.py b/lib/ansible/utils/template.py index 9180f47326..2491035c59 100644 --- a/lib/ansible/utils/template.py +++ b/lib/ansible/utils/template.py @@ -497,7 +497,14 @@ def template_from_file(basedir, path, vars): if data.endswith('\n') and not res.endswith('\n'): res = res + '\n' - return template(basedir, res, vars) + + if isinstance(res, unicode): + # do not try to re-template a unicode string + result = res + else: + result = template(basedir, res, vars) + + return result def template_from_string(basedir, data, vars, fail_on_undefined=False): ''' run a string through the (Jinja2) templating engine ''' diff --git a/library/cloud/digital_ocean b/library/cloud/digital_ocean index 400bf3bf54..bd975cf1b9 100644 --- a/library/cloud/digital_ocean +++ b/library/cloud/digital_ocean @@ -112,7 +112,7 @@ EXAMPLES = ''' - digital_ocean: > state=present command=droplet - name=my_new_droplet + name=mydroplet client_id=XXX api_key=XXX size_id=1 @@ -131,7 +131,7 @@ EXAMPLES = ''' state=present command=droplet id=123 - name=my_new_droplet + name=mydroplet client_id=XXX api_key=XXX size_id=1 @@ -147,7 +147,7 @@ EXAMPLES = ''' - digital_ocean: > state=present ssh_key_ids=id1,id2 - name=my_new_droplet + name=mydroplet client_id=XXX api_key=XXX size_id=1 @@ -221,7 +221,7 @@ class Droplet(JsonfyMixIn): raise TimeoutError('Wait for droplet running timeout', self.id) def destroy(self): - return self.manager.destroy_droplet(self.id) + return self.manager.destroy_droplet(self.id, scrub_data=True) @classmethod def setup(cls, client_id, api_key): diff --git a/library/cloud/docker b/library/cloud/docker index 9abf63e18a..d4fb1f372a 100644 --- a/library/cloud/docker +++ b/library/cloud/docker @@ -1,5 +1,4 @@ -#!/usr/bin/env python -# +#!/usr/bin/python # (c) 2013, Cove Schneider # diff --git a/library/cloud/ec2 b/library/cloud/ec2 index 0e0b8aaf0f..a565b0359a 100644 --- a/library/cloud/ec2 +++ b/library/cloud/ec2 @@ -294,15 +294,6 @@ local_action: import sys import time -AWS_REGIONS = ['ap-northeast-1', - 'ap-southeast-1', - 'ap-southeast-2', - 'eu-west-1', - 'sa-east-1', - 'us-east-1', - 'us-west-1', - 'us-west-2'] - try: import boto.ec2 from boto.exception import EC2ResponseError @@ -653,24 +644,7 @@ def main(): ) ) - # def get_ec2_creds(module): - # return ec2_url, ec2_access_key, ec2_secret_key, region - ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) - - # If we have a region specified, connect to its endpoint. - if region: - try: - ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) - # If we specified an ec2_url then try connecting to it - elif ec2_url: - try: - ec2 = boto.connect_ec2_endpoint(ec2_url, aws_access_key, aws_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) - else: - module.fail_json(msg="Either region or ec2_url must be specified") + ec2 = ec2_connect(module) if module.params.get('state') == 'absent': instance_ids = module.params.get('instance_ids') diff --git a/library/cloud/ec2_ami b/library/cloud/ec2_ami index ea7e0ad86d..f90f23db99 100644 --- a/library/cloud/ec2_ami +++ b/library/cloud/ec2_ami @@ -156,15 +156,6 @@ EXAMPLES = ''' import sys import time -AWS_REGIONS = ['ap-northeast-1', - 'ap-southeast-1', - 'ap-southeast-2', - 'eu-west-1', - 'sa-east-1', - 'us-east-1', - 'us-west-1', - 'us-west-2'] - try: import boto import boto.ec2 @@ -279,24 +270,7 @@ def main(): ) ) - # def get_ec2_creds(module): - # return ec2_url, ec2_access_key, ec2_secret_key, region - ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) - - # If we have a region specified, connect to its endpoint. - if region: - try: - ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) - # If we specified an ec2_url then try connecting to it - elif ec2_url: - try: - ec2 = boto.connect_ec2_endpoint(ec2_url, aws_access_key, aws_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) - else: - module.fail_json(msg="Either region or ec2_url must be specified") + ec2 = ec2_connect(module) if module.params.get('state') == 'absent': if not module.params.get('image_id'): diff --git a/library/cloud/ec2_eip b/library/cloud/ec2_eip index 1c5db8cf4c..4399c6bdf6 100644 --- a/library/cloud/ec2_eip +++ b/library/cloud/ec2_eip @@ -102,38 +102,6 @@ else: boto_found = True -def connect(ec2_url, ec2_access_key, ec2_secret_key, region, module): - - """ Return an ec2 connection""" - # allow environment variables to be used if ansible vars aren't set - if not ec2_url and 'EC2_URL' in os.environ: - ec2_url = os.environ['EC2_URL'] - if not ec2_secret_key and 'EC2_SECRET_KEY' in os.environ: - ec2_secret_key = os.environ['EC2_SECRET_KEY'] - if not ec2_access_key and 'EC2_ACCESS_KEY' in os.environ: - ec2_access_key = os.environ['EC2_ACCESS_KEY'] - - # If we have a region specified, connect to its endpoint. - if region: - try: - ec2 = boto.ec2.connect_to_region(region, - aws_access_key_id=ec2_access_key, - aws_secret_access_key=ec2_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(" %s %s %s " % (region, ec2_access_key, - ec2_secret_key))) - # Otherwise, no region so we fallback to the old connection method - else: - try: - if ec2_url: # if we have an URL set, connect to the specified endpoint - ec2 = boto.connect_ec2_endpoint(ec2_url, ec2_access_key, ec2_secret_key) - else: # otherwise it's Amazon. - ec2 = boto.connect_ec2(ec2_access_key, ec2_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) - return ec2 - - def associate_ip_and_instance(ec2, address, instance_id, module): if ip_is_associated_with_instance(ec2, address.public_ip, instance_id, module): module.exit_json(changed=False, public_ip=address.public_ip) @@ -248,8 +216,8 @@ def main(): state = dict(required=False, default='present', choices=['present', 'absent']), ec2_url = dict(required=False, aliases=['EC2_URL']), - ec2_secret_key = dict(required=False, aliases=['EC2_SECRET_KEY'], no_log=True), - ec2_access_key = dict(required=False, aliases=['EC2_ACCESS_KEY']), + ec2_secret_key = dict(aliases=['aws_secret_key', 'secret_key'], no_log=True), + ec2_access_key = dict(aliases=['aws_access_key', 'access_key']), region = dict(required=False, aliases=['ec2_region']), in_vpc = dict(required=False, choices=BOOLEANS, default=False), ), @@ -259,13 +227,7 @@ def main(): if not boto_found: module.fail_json(msg="boto is required") - ec2_url, ec2_access_key, ec2_secret_key, region = get_ec2_creds(module) - - ec2 = connect(ec2_url, - ec2_access_key, - ec2_secret_key, - region, - module) + ec2 = ec2_connect(module) instance_id = module.params.get('instance_id') public_ip = module.params.get('public_ip') diff --git a/library/cloud/ec2_elb b/library/cloud/ec2_elb index 3132d9e951..6a8a25986e 100644 --- a/library/cloud/ec2_elb +++ b/library/cloud/ec2_elb @@ -17,9 +17,9 @@ DOCUMENTATION = """ --- module: ec2_elb -short_description: De-registers or registers instances from EC2 ELB(s) +short_description: De-registers or registers instances from EC2 ELBs description: - - This module de-registers or registers an AWS EC2 instance from the ELB(s) + - This module de-registers or registers an AWS EC2 instance from the ELBs that it belongs to. - Returns fact "ec2_elbs" which is a list of elbs attached to the instance if state=absent is passed as an argument. @@ -32,6 +32,7 @@ options: description: - register or deregister the instance required: true + choices: ['present', 'absent'] instance_id: description: @@ -102,15 +103,6 @@ import time import sys import os -AWS_REGIONS = ['ap-northeast-1', - 'ap-southeast-1', - 'ap-southeast-2', - 'eu-west-1', - 'sa-east-1', - 'us-east-1', - 'us-west-1', - 'us-west-2'] - try: import boto import boto.ec2 diff --git a/library/cloud/ec2_group b/library/cloud/ec2_group index c325c1ce30..bdba3f5b05 100644 --- a/library/cloud/ec2_group +++ b/library/cloud/ec2_group @@ -113,16 +113,12 @@ def main(): ec2_url=dict(aliases=['EC2_URL']), ec2_secret_key=dict(aliases=['EC2_SECRET_KEY', 'aws_secret_key'], no_log=True), ec2_access_key=dict(aliases=['EC2_ACCESS_KEY', 'aws_access_key']), - region=dict(choices=['eu-west-1', 'sa-east-1', 'us-east-1', 'ap-northeast-1', 'us-west-2', 'us-west-1', 'ap-southeast-1', 'ap-southeast-2']), + region=dict(choices=AWS_REGIONS), state = dict(default='present', choices=['present', 'absent']), ), supports_check_mode=True, ) - # def get_ec2_creds(module): - # return ec2_url, ec2_access_key, ec2_secret_key, region - ec2_url, ec2_access_key, ec2_secret_key, region = get_ec2_creds(module) - name = module.params['name'] description = module.params['description'] vpc_id = module.params['vpc_id'] @@ -131,21 +127,7 @@ def main(): changed = False - # If we have a region specified, connect to its endpoint. - if region: - try: - ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=ec2_access_key, aws_secret_access_key=ec2_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg=str(e)) - # Otherwise, no region so we fallback to the old connection method - else: - try: - if ec2_url: # if we have an URL set, connect to the specified endpoint - ec2 = boto.connect_ec2_endpoint(ec2_url, ec2_access_key, ec2_secret_key) - else: # otherwise it's Amazon. - ec2 = boto.connect_ec2(ec2_access_key, ec2_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg=str(e)) + ec2 = ec2_connect(module) # find the group if present group = None diff --git a/library/cloud/ec2_tag b/library/cloud/ec2_tag index 53d7af18cc..7c7d8171e3 100644 --- a/library/cloud/ec2_tag +++ b/library/cloud/ec2_tag @@ -102,15 +102,6 @@ except ImportError: print "failed=True msg='boto required for this module'" sys.exit(1) -AWS_REGIONS = ['ap-northeast-1', - 'ap-southeast-1', - 'ap-southeast-2', - 'eu-west-1', - 'sa-east-1', - 'us-east-1', - 'us-west-1', - 'us-west-2'] - def main(): module = AnsibleModule( argument_spec = dict( @@ -124,28 +115,11 @@ def main(): ) ) - # def get_ec2_creds(module): - # return ec2_url, ec2_access_key, ec2_secret_key, region - ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) - resource = module.params.get('resource') tags = module.params['tags'] state = module.params.get('state') - # If we have a region specified, connect to its endpoint. - if region: - try: - ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) - # Otherwise, no region so we fallback to the old connection method - elif ec2_url: - try: - ec2 = boto.connect_ec2_endpoint(ec2_url, aws_access_key, aws_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) - else: - module.fail_json(msg="Either region or ec2_url must be specified") + ec2 = ec2_connect(module) # We need a comparison here so that we can accurately report back changed status. # Need to expand the gettags return format and compare with "tags" and then tag or detag as appropriate. diff --git a/library/cloud/ec2_vol b/library/cloud/ec2_vol index e5c7d1eab1..a60e0b71f8 100644 --- a/library/cloud/ec2_vol +++ b/library/cloud/ec2_vol @@ -127,15 +127,6 @@ except ImportError: print "failed=True msg='boto required for this module'" sys.exit(1) -AWS_REGIONS = ['ap-northeast-1', - 'ap-southeast-1', - 'ap-southeast-2', - 'eu-west-1', - 'sa-east-1', - 'us-east-1', - 'us-west-1', - 'us-west-2'] - def main(): module = AnsibleModule( argument_spec = dict( @@ -151,30 +142,13 @@ def main(): ) ) - # def get_ec2_creds(module): - # return ec2_url, ec2_access_key, ec2_secret_key, region - ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) - instance = module.params.get('instance') volume_size = module.params.get('volume_size') iops = module.params.get('iops') device_name = module.params.get('device_name') zone = module.params.get('zone') - # If we have a region specified, connect to its endpoint. - if region: - try: - ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) - # Otherwise, no region so we fallback to the old connection method - elif ec2_url: - try: - ec2 = boto.connect_ec2_endpoint(ec2_url, aws_access_key, aws_secret_key) - except boto.exception.NoAuthHandlerFound, e: - module.fail_json(msg = str(e)) - else: - module.fail_json(msg="Either region or ec2_url must be specified") + ec2 = ec2_connect(module) # Here we need to get the zone info for the instance. This covers situation where # instance is specified but zone isn't. diff --git a/library/cloud/ec2_vpc b/library/cloud/ec2_vpc index 663a574f95..53b60c9dfc 100644 --- a/library/cloud/ec2_vpc +++ b/library/cloud/ec2_vpc @@ -164,15 +164,6 @@ except ImportError: print "failed=True msg='boto required for this module'" sys.exit(1) -AWS_REGIONS = ['ap-northeast-1', - 'ap-southeast-1', - 'ap-southeast-2', - 'eu-west-1', - 'sa-east-1', - 'us-east-1', - 'us-west-1', - 'us-west-2'] - def get_vpc_info(vpc): """ Retrieves vpc information from an instance diff --git a/library/cloud/elasticache b/library/cloud/elasticache index 9b40107d98..a54deafc25 100644 --- a/library/cloud/elasticache +++ b/library/cloud/elasticache @@ -17,9 +17,9 @@ DOCUMENTATION = """ --- module: elasticache +short_description: Manage cache clusters in Amazon Elasticache. description: - Manage cache clusters in Amazon Elasticache. -short_description: Manage cache clusters in Amazon Elasticache. - Returns information about the specified cache cluster. version_added: "1.4" requirements: [ "boto" ] @@ -137,15 +137,6 @@ import sys import os import time -AWS_REGIONS = ['ap-northeast-1', - 'ap-southeast-1', - 'ap-southeast-2', - 'eu-west-1', - 'sa-east-1', - 'us-east-1', - 'us-west-1', - 'us-west-2'] - try: import boto from boto.elasticache.layer1 import ElastiCacheConnection diff --git a/library/cloud/quantum_floating_ip b/library/cloud/quantum_floating_ip index 1d755e67c2..531ddfe2b8 100644 --- a/library/cloud/quantum_floating_ip +++ b/library/cloud/quantum_floating_ip @@ -18,11 +18,14 @@ try: from novaclient.v1_1 import client as nova_client - from quantumclient.quantum import client + try: + from neutronclient.neutron import client + except ImportError: + from quantumclient.quantum import client from keystoneclient.v2_0 import client as ksclient import time except ImportError: - print("failed=True msg='glanceclient,keystoneclient and quantumclient client are required'") + print("failed=True msg='novaclient,keystoneclient and quantumclient (or neutronclient) are required'") DOCUMENTATION = ''' --- @@ -64,7 +67,7 @@ options: default: present network_name: description: - - Name of the network from which IP has to be assigned to VM. Please make sure the network is an external network + - Name of the network from which IP has to be assigned to VM. Please make sure the network is an external network required: true default: None instance_name: @@ -72,14 +75,19 @@ options: - The name of the instance to which the IP address should be assigned required: true default: None -requirements: ["novaclient", "quantumclient", "keystoneclient"] + internal_network_name: + description: + - The name of the network of the port to associate with the floating ip. Necessary when VM multiple networks. + required: false + default: None +requirements: ["novaclient", "quantumclient", "neutronclient", "keystoneclient"] ''' EXAMPLES = ''' # Assign a floating ip to the instance from an external network - quantum_floating_ip: state=present login_username=admin login_password=admin login_tenant_name=admin network_name=external_network - instance_name=vm1 + instance_name=vm1 internal_network_name=internal_network ''' def _get_ksclient(module, kwargs): @@ -99,10 +107,10 @@ def _get_endpoint(module, ksclient): try: endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') except Exception as e: - module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + module.fail_json(msg = "Error getting network endpoint: %s" % e.message) return endpoint -def _get_quantum_client(module, kwargs): +def _get_neutron_client(module, kwargs): _ksclient = _get_ksclient(module, kwargs) token = _ksclient.auth_token endpoint = _get_endpoint(module, _ksclient) @@ -111,10 +119,10 @@ def _get_quantum_client(module, kwargs): 'endpoint_url': endpoint } try: - quantum = client.Client('2.0', **kwargs) + neutron = client.Client('2.0', **kwargs) except Exception as e: - module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) - return quantum + module.fail_json(msg = "Error in connecting to neutron: %s " % e.message) + return neutron def _get_server_state(module, nova): server_info = None @@ -130,68 +138,81 @@ def _get_server_state(module, nova): break except Exception as e: module.fail_json(msg = "Error in getting the server list: %s" % e.message) - return server_info, server - -def _get_port_info(quantum, module, instance_id): + return server_info, server + +def _get_port_info(neutron, module, instance_id, internal_network_name=None): + if internal_network_name: + kwargs = { + 'name': internal_network_name, + } + networks = neutron.list_networks(**kwargs) + subnet_id = networks['networks'][0]['subnets'][0] kwargs = { 'device_id': instance_id, } try: - ports = quantum.list_ports(**kwargs) + ports = neutron.list_ports(**kwargs) except Exception as e: module.fail_json( msg = "Error in listing ports: %s" % e.message) + if subnet_id: + port = next(port for port in ports['ports'] if port['fixed_ips'][0]['subnet_id'] == subnet_id) + port_id = port['id'] + fixed_ip_address = port['fixed_ips'][0]['ip_address'] + else: + port_id = ports['ports'][0]['id'] + fixed_ip_address = ports['ports'][0]['fixed_ips'][0]['ip_address'] if not ports['ports']: return None, None - return ports['ports'][0]['fixed_ips'][0]['ip_address'], ports['ports'][0]['id'] - -def _get_floating_ip(module, quantum, fixed_ip_address): + return fixed_ip_address, port_id + +def _get_floating_ip(module, neutron, fixed_ip_address): kwargs = { 'fixed_ip_address': fixed_ip_address } try: - ips = quantum.list_floatingips(**kwargs) + ips = neutron.list_floatingips(**kwargs) except Exception as e: module.fail_json(msg = "error in fetching the floatingips's %s" % e.message) if not ips['floatingips']: return None, None return ips['floatingips'][0]['id'], ips['floatingips'][0]['floating_ip_address'] -def _create_floating_ip(quantum, module, port_id, net_id): +def _create_floating_ip(neutron, module, port_id, net_id): kwargs = { 'port_id': port_id, 'floating_network_id': net_id } try: - result = quantum.create_floatingip({'floatingip': kwargs}) + result = neutron.create_floatingip({'floatingip': kwargs}) except Exception as e: module.fail_json(msg="There was an error in updating the floating ip address: %s" % e.message) module.exit_json(changed=True, result=result, public_ip=result['floatingip']['floating_ip_address']) -def _get_net_id(quantum, module): +def _get_net_id(neutron, module): kwargs = { 'name': module.params['network_name'], } try: - networks = quantum.list_networks(**kwargs) + networks = neutron.list_networks(**kwargs) except Exception as e: - module.fail_json("Error in listing quantum networks: %s" % e.message) + module.fail_json("Error in listing neutron networks: %s" % e.message) if not networks['networks']: return None return networks['networks'][0]['id'] -def _update_floating_ip(quantum, module, port_id, floating_ip_id): +def _update_floating_ip(neutron, module, port_id, floating_ip_id): kwargs = { 'port_id': port_id } try: - result = quantum.update_floatingip(floating_ip_id, {'floatingip': kwargs}) + result = neutron.update_floatingip(floating_ip_id, {'floatingip': kwargs}) except Exception as e: module.fail_json(msg="There was an error in updating the floating ip address: %s" % e.message) module.exit_json(changed=True, result=result) def main(): - + module = AnsibleModule( argument_spec = dict( login_username = dict(default='admin'), @@ -200,39 +221,40 @@ def main(): auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), region_name = dict(default=None), network_name = dict(required=True), - instance_name = dict(required=True), - state = dict(default='present', choices=['absent', 'present']) + instance_name = dict(required=True), + state = dict(default='present', choices=['absent', 'present']), + internal_network_name = dict(default=None), ), ) - + try: - nova = nova_client.Client(module.params['login_username'], module.params['login_password'], + nova = nova_client.Client(module.params['login_username'], module.params['login_password'], module.params['login_tenant_name'], module.params['auth_url'], service_type='compute') - quantum = _get_quantum_client(module, module.params) + neutron = _get_neutron_client(module, module.params) except Exception as e: module.fail_json(msg="Error in authenticating to nova: %s" % e.message) - + server_info, server_obj = _get_server_state(module, nova) if not server_info: module.fail_json(msg="The instance name provided cannot be found") - fixed_ip, port_id = _get_port_info(quantum, module, server_info['id']) + fixed_ip, port_id = _get_port_info(neutron, module, server_info['id'], module.params['internal_network_name']) if not port_id: module.fail_json(msg="Cannot find a port for this instance, maybe fixed ip is not assigned") - floating_id, floating_ip = _get_floating_ip(module, quantum, fixed_ip) + floating_id, floating_ip = _get_floating_ip(module, neutron, fixed_ip) if module.params['state'] == 'present': if floating_ip: module.exit_json(changed = False, public_ip=floating_ip) - net_id = _get_net_id(quantum, module) + net_id = _get_net_id(neutron, module) if not net_id: module.fail_json(msg = "cannot find the network specified, please check") - _create_floating_ip(quantum, module, port_id, net_id) + _create_floating_ip(neutron, module, port_id, net_id) if module.params['state'] == 'absent': if floating_ip: - _update_floating_ip(quantum, module, None, floating_id) + _update_floating_ip(neutron, module, None, floating_id) module.exit_json(changed=False) # this is magic, see lib/ansible/module.params['common.py diff --git a/library/cloud/quantum_floating_ip_associate b/library/cloud/quantum_floating_ip_associate index 9b720ea232..e878fe5086 100644 --- a/library/cloud/quantum_floating_ip_associate +++ b/library/cloud/quantum_floating_ip_associate @@ -18,11 +18,14 @@ try: from novaclient.v1_1 import client as nova_client - from quantumclient.quantum import client + try: + from neutronclient.neutron import client + except ImportError: + from quantumclient.quantum import client from keystoneclient.v2_0 import client as ksclient import time except ImportError: - print "failed=True msg='glanceclient,novaclient and keystone client are required'" + print "failed=True msg='novaclient, keystone, and quantumclient (or neutronclient) client are required'" DOCUMENTATION = ''' --- @@ -72,7 +75,7 @@ options: - floating ip that should be assigned to the instance required: true default: None -requirements: ["quantumclient", "keystoneclient"] +requirements: ["quantumclient", "neutronclient", "keystoneclient"] ''' EXAMPLES = ''' @@ -103,10 +106,10 @@ def _get_endpoint(module, ksclient): try: endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') except Exception as e: - module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + module.fail_json(msg = "Error getting network endpoint: %s" % e.message) return endpoint -def _get_quantum_client(module, kwargs): +def _get_neutron_client(module, kwargs): _ksclient = _get_ksclient(module, kwargs) token = _ksclient.auth_token endpoint = _get_endpoint(module, _ksclient) @@ -115,10 +118,10 @@ def _get_quantum_client(module, kwargs): 'endpoint_url': endpoint } try: - quantum = client.Client('2.0', **kwargs) + neutron = client.Client('2.0', **kwargs) except Exception as e: - module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) - return quantum + module.fail_json(msg = "Error in connecting to neutron: %s " % e.message) + return neutron def _get_server_state(module, nova): server_info = None @@ -134,24 +137,24 @@ def _get_server_state(module, nova): break except Exception as e: module.fail_json(msg = "Error in getting the server list: %s" % e.message) - return server_info, server - -def _get_port_id(quantum, module, instance_id): + return server_info, server + +def _get_port_id(neutron, module, instance_id): kwargs = dict(device_id = instance_id) try: - ports = quantum.list_ports(**kwargs) + ports = neutron.list_ports(**kwargs) except Exception as e: module.fail_json( msg = "Error in listing ports: %s" % e.message) if not ports['ports']: return None return ports['ports'][0]['id'] - -def _get_floating_ip_id(module, quantum): + +def _get_floating_ip_id(module, neutron): kwargs = { 'floating_ip_address': module.params['ip_address'] } try: - ips = quantum.list_floatingips(**kwargs) + ips = neutron.list_floatingips(**kwargs) except Exception as e: module.fail_json(msg = "error in fetching the floatingips's %s" % e.message) if not ips['floatingips']: @@ -163,18 +166,18 @@ def _get_floating_ip_id(module, quantum): state = "attached" return state, ip -def _update_floating_ip(quantum, module, port_id, floating_ip_id): +def _update_floating_ip(neutron, module, port_id, floating_ip_id): kwargs = { 'port_id': port_id } try: - result = quantum.update_floatingip(floating_ip_id, {'floatingip': kwargs}) + result = neutron.update_floatingip(floating_ip_id, {'floatingip': kwargs}) except Exception as e: module.fail_json(msg = "There was an error in updating the floating ip address: %s" % e.message) module.exit_json(changed = True, result = result, public_ip=module.params['ip_address']) def main(): - + module = AnsibleModule( argument_spec = dict( login_username = dict(default='admin'), @@ -183,33 +186,34 @@ def main(): auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), region_name = dict(default=None), ip_address = dict(required=True), - instance_name = dict(required=True), + instance_name = dict(required=True), state = dict(default='present', choices=['absent', 'present']) ), ) - + try: - nova = nova_client.Client(module.params['login_username'], module.params['login_password'], module.params['login_tenant_name'], module.params['auth_url'], service_type='compute') + nova = nova_client.Client(module.params['login_username'], module.params['login_password'], + module.params['login_tenant_name'], module.params['auth_url'], service_type='compute') except Exception as e: module.fail_json( msg = " Error in authenticating to nova: %s" % e.message) - quantum = _get_quantum_client(module, module.params) - state, floating_ip_id = _get_floating_ip_id(module, quantum) + neutron = _get_neutron_client(module, module.params) + state, floating_ip_id = _get_floating_ip_id(module, neutron) if module.params['state'] == 'present': if state == 'attached': module.exit_json(changed = False, result = 'attached', public_ip=module.params['ip_address']) server_info, server_obj = _get_server_state(module, nova) if not server_info: module.fail_json(msg = " The instance name provided cannot be found") - port_id = _get_port_id(quantum, module, server_info['id']) + port_id = _get_port_id(neutron, module, server_info['id']) if not port_id: module.fail_json(msg = "Cannot find a port for this instance, maybe fixed ip is not assigned") - _update_floating_ip(quantum, module, port_id, floating_ip_id) + _update_floating_ip(neutron, module, port_id, floating_ip_id) if module.params['state'] == 'absent': if state == 'detached': module.exit_json(changed = False, result = 'detached') if state == 'attached': - _update_floating_ip(quantum, module, None, floating_ip_id) + _update_floating_ip(neutron, module, None, floating_ip_id) module.exit_json(changed = True, result = "detached") # this is magic, see lib/ansible/module.params['common.py diff --git a/library/cloud/quantum_network b/library/cloud/quantum_network index 4eb416f752..be6951c0b7 100644 --- a/library/cloud/quantum_network +++ b/library/cloud/quantum_network @@ -17,10 +17,13 @@ # along with this software. If not, see <http://www.gnu.org/licenses/>. try: - from quantumclient.quantum import client + try: + from neutronclient.neutron import client + except ImportError: + from quantumclient.quantum import client from keystoneclient.v2_0 import client as ksclient except ImportError: - print("failed=True msg='quantumclient and keystone client are required'") + print("failed=True msg='quantumclient (or neutronclient) and keystone client are required'") DOCUMENTATION = ''' --- @@ -67,7 +70,7 @@ options: default: present name: description: - - Name to be assigned to the nework + - Name to be assigned to the nework required: true default: None provider_network_type: @@ -100,7 +103,7 @@ options: - Whether the state should be marked as up or down required: false default: true -requirements: ["quantumclient", "keystoneclient"] +requirements: ["quantumclient", "neutronclient", "keystoneclient"] ''' @@ -125,21 +128,21 @@ def _get_ksclient(module, kwargs): password=kwargs.get('login_password'), tenant_name=kwargs.get('login_tenant_name'), auth_url=kwargs.get('auth_url')) - except Exception as e: + except Exception as e: module.fail_json(msg = "Error authenticating to the keystone: %s" %e.message) - global _os_keystone + global _os_keystone _os_keystone = kclient - return kclient - + return kclient + def _get_endpoint(module, ksclient): try: endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') except Exception as e: - module.fail_json(msg = "Error getting endpoint for Quantum: %s " %e.message) + module.fail_json(msg = "Error getting network endpoint: %s " %e.message) return endpoint -def _get_quantum_client(module, kwargs): +def _get_neutron_client(module, kwargs): _ksclient = _get_ksclient(module, kwargs) token = _ksclient.auth_token endpoint = _get_endpoint(module, _ksclient) @@ -148,10 +151,10 @@ def _get_quantum_client(module, kwargs): 'endpoint_url': endpoint } try: - quantum = client.Client('2.0', **kwargs) + neutron = client.Client('2.0', **kwargs) except Exception as e: - module.fail_json(msg = " Error in connecting to quantum: %s " %e.message) - return quantum + module.fail_json(msg = " Error in connecting to neutron: %s " %e.message) + return neutron def _set_tenant_id(module): global _os_tenant_id @@ -159,7 +162,7 @@ def _set_tenant_id(module): tenant_name = module.params['login_tenant_name'] else: tenant_name = module.params['tenant_name'] - + for tenant in _os_keystone.tenants.list(): if tenant.name == tenant_name: _os_tenant_id = tenant.id @@ -168,22 +171,22 @@ def _set_tenant_id(module): module.fail_json(msg = "The tenant id cannot be found, please check the paramters") -def _get_net_id(quantum, module): +def _get_net_id(neutron, module): kwargs = { 'tenant_id': _os_tenant_id, 'name': module.params['name'], } try: - networks = quantum.list_networks(**kwargs) + networks = neutron.list_networks(**kwargs) except Exception as e: - module.fail_json(msg = "Error in listing quantum networks: %s" % e.message) - if not networks['networks']: + module.fail_json(msg = "Error in listing neutron networks: %s" % e.message) + if not networks['networks']: return None return networks['networks'][0]['id'] -def _create_network(module, quantum): +def _create_network(module, neutron): - quantum.format = 'json' + neutron.format = 'json' network = { 'name': module.params.get('name'), @@ -212,21 +215,21 @@ def _create_network(module, quantum): network.pop('provider:segmentation_id', None) try: - net = quantum.create_network({'network':network}) + net = neutron.create_network({'network':network}) except Exception as e: module.fail_json(msg = "Error in creating network: %s" % e.message) return net['network']['id'] - -def _delete_network(module, net_id, quantum): + +def _delete_network(module, net_id, neutron): try: - id = quantum.delete_network(net_id) - except Exception as e: + id = neutron.delete_network(net_id) + except Exception as e: module.fail_json(msg = "Error in deleting the network: %s" % e.message) return True def main(): - + module = AnsibleModule( argument_spec = dict( login_username = dict(default='admin'), @@ -237,8 +240,8 @@ def main(): name = dict(required=True), tenant_name = dict(default=None), provider_network_type = dict(default=None, choices=['local', 'vlan', 'flat', 'gre']), - provider_physical_network = dict(default=None), - provider_segmentation_id = dict(default=None), + provider_physical_network = dict(default=None), + provider_segmentation_id = dict(default=None), router_external = dict(default=False, type='bool'), shared = dict(default=False, type='bool'), admin_state_up = dict(default=True, type='bool'), @@ -254,24 +257,24 @@ def main(): if not module.params['provider_segmentation_id']: module.fail_json(msg = " for vlan & gre networks, variable provider_segmentation_id should be set.") - quantum = _get_quantum_client(module, module.params) + neutron = _get_neutron_client(module, module.params) - _set_tenant_id(module) + _set_tenant_id(module) - if module.params['state'] == 'present': - network_id = _get_net_id(quantum, module) + if module.params['state'] == 'present': + network_id = _get_net_id(neutron, module) if not network_id: - network_id = _create_network(module, quantum) + network_id = _create_network(module, neutron) module.exit_json(changed = True, result = "Created", id = network_id) else: module.exit_json(changed = False, result = "Success", id = network_id) if module.params['state'] == 'absent': - network_id = _get_net_id(quantum, module) + network_id = _get_net_id(neutron, module) if not network_id: module.exit_json(changed = False, result = "Success") else: - _delete_network(module, network_id, quantum) + _delete_network(module, network_id, neutron) module.exit_json(changed = True, result = "Deleted") # this is magic, see lib/ansible/module.params['common.py diff --git a/library/cloud/quantum_router b/library/cloud/quantum_router index 26387de205..9d31da2f19 100644 --- a/library/cloud/quantum_router +++ b/library/cloud/quantum_router @@ -17,10 +17,13 @@ # along with this software. If not, see <http://www.gnu.org/licenses/>. try: - from quantumclient.quantum import client + try: + from neutronclient.neutron import client + except ImportError: + from quantumclient.quantum import client from keystoneclient.v2_0 import client as ksclient except ImportError: - print("failed=True msg='quantumclient and keystone client are required'") + print("failed=True msg='quantumclient (or neutronclient) and keystone client are required'") DOCUMENTATION = ''' --- @@ -75,7 +78,7 @@ options: - desired admin state of the created router . required: false default: true -requirements: ["quantumclient", "keystoneclient"] +requirements: ["quantumclient", "neutronclient", "keystoneclient"] ''' EXAMPLES = ''' @@ -96,21 +99,21 @@ def _get_ksclient(module, kwargs): password=kwargs.get('login_password'), tenant_name=kwargs.get('login_tenant_name'), auth_url=kwargs.get('auth_url')) - except Exception as e: + except Exception as e: module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message) - global _os_keystone + global _os_keystone _os_keystone = kclient - return kclient - + return kclient + def _get_endpoint(module, ksclient): try: endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') except Exception as e: - module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + module.fail_json(msg = "Error getting network endpoint: %s" % e.message) return endpoint -def _get_quantum_client(module, kwargs): +def _get_neutron_client(module, kwargs): _ksclient = _get_ksclient(module, kwargs) token = _ksclient.auth_token endpoint = _get_endpoint(module, _ksclient) @@ -119,10 +122,10 @@ def _get_quantum_client(module, kwargs): 'endpoint_url': endpoint } try: - quantum = client.Client('2.0', **kwargs) + neutron = client.Client('2.0', **kwargs) except Exception as e: - module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) - return quantum + module.fail_json(msg = "Error in connecting to neutron: %s " % e.message) + return neutron def _set_tenant_id(module): global _os_tenant_id @@ -139,38 +142,38 @@ def _set_tenant_id(module): module.fail_json(msg = "The tenant id cannot be found, please check the paramters") -def _get_router_id(module, quantum): +def _get_router_id(module, neutron): kwargs = { 'name': module.params['name'], 'tenant_id': _os_tenant_id, } try: - routers = quantum.list_routers(**kwargs) + routers = neutron.list_routers(**kwargs) except Exception as e: module.fail_json(msg = "Error in getting the router list: %s " % e.message) if not routers['routers']: return None return routers['routers'][0]['id'] -def _create_router(module, quantum): +def _create_router(module, neutron): router = { 'name': module.params['name'], 'tenant_id': _os_tenant_id, 'admin_state_up': module.params['admin_state_up'], } try: - new_router = quantum.create_router(dict(router=router)) + new_router = neutron.create_router(dict(router=router)) except Exception as e: module.fail_json( msg = "Error in creating router: %s" % e.message) return new_router['router']['id'] -def _delete_router(module, quantum, router_id): +def _delete_router(module, neutron, router_id): try: - quantum.delete_router(router_id) + neutron.delete_router(router_id) except: module.fail_json("Error in deleting the router") return True - + def main(): module = AnsibleModule( argument_spec = dict( @@ -185,26 +188,26 @@ def main(): admin_state_up = dict(type='bool', default=True), ), ) - - quantum = _get_quantum_client(module, module.params) + + neutron = _get_neutron_client(module, module.params) _set_tenant_id(module) if module.params['state'] == 'present': - router_id = _get_router_id(module, quantum) + router_id = _get_router_id(module, neutron) if not router_id: - router_id = _create_router(module, quantum) + router_id = _create_router(module, neutron) module.exit_json(changed=True, result="Created", id=router_id) else: module.exit_json(changed=False, result="success" , id=router_id) else: - router_id = _get_router_id(module, quantum) + router_id = _get_router_id(module, neutron) if not router_id: module.exit_json(changed=False, result="success") else: - _delete_router(module, quantum, router_id) + _delete_router(module, neutron, router_id) module.exit_json(changed=True, result="deleted") - + # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * main() diff --git a/library/cloud/quantum_router_gateway b/library/cloud/quantum_router_gateway index 60d500e6f6..68372e785d 100644 --- a/library/cloud/quantum_router_gateway +++ b/library/cloud/quantum_router_gateway @@ -17,10 +17,13 @@ # along with this software. If not, see <http://www.gnu.org/licenses/>. try: - from quantumclient.quantum import client + try: + from neutronclient.neutron import client + except ImportError: + from quantumclient.quantum import client from keystoneclient.v2_0 import client as ksclient except ImportError: - print("failed=True msg='quantumclient and keystone client are required'") + print("failed=True msg='quantumclient (or neutronclient) and keystone client are required'") DOCUMENTATION = ''' --- module: quantum_router_gateway @@ -69,7 +72,7 @@ options: - Name of the external network which should be attached to the router. required: true default: None -requirements: ["quantumclient", "keystoneclient"] +requirements: ["quantumclient", "neutronclient", "keystoneclient"] ''' EXAMPLES = ''' @@ -86,21 +89,21 @@ def _get_ksclient(module, kwargs): password=kwargs.get('login_password'), tenant_name=kwargs.get('login_tenant_name'), auth_url=kwargs.get('auth_url')) - except Exception as e: + except Exception as e: module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message) - global _os_keystone + global _os_keystone _os_keystone = kclient - return kclient - + return kclient + def _get_endpoint(module, ksclient): try: endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') except Exception as e: - module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + module.fail_json(msg = "Error getting network endpoint: %s" % e.message) return endpoint -def _get_quantum_client(module, kwargs): +def _get_neutron_client(module, kwargs): _ksclient = _get_ksclient(module, kwargs) token = _ksclient.auth_token endpoint = _get_endpoint(module, _ksclient) @@ -109,68 +112,68 @@ def _get_quantum_client(module, kwargs): 'endpoint_url': endpoint } try: - quantum = client.Client('2.0', **kwargs) + neutron = client.Client('2.0', **kwargs) except Exception as e: - module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) - return quantum + module.fail_json(msg = "Error in connecting to neutron: %s " % e.message) + return neutron -def _get_router_id(module, quantum): +def _get_router_id(module, neutron): kwargs = { 'name': module.params['router_name'], } try: - routers = quantum.list_routers(**kwargs) + routers = neutron.list_routers(**kwargs) except Exception as e: module.fail_json(msg = "Error in getting the router list: %s " % e.message) if not routers['routers']: return None return routers['routers'][0]['id'] -def _get_net_id(quantum, module): +def _get_net_id(neutron, module): kwargs = { 'name': module.params['network_name'], 'router:external': True } try: - networks = quantum.list_networks(**kwargs) + networks = neutron.list_networks(**kwargs) except Exception as e: - module.fail_json("Error in listing quantum networks: %s" % e.message) + module.fail_json("Error in listing neutron networks: %s" % e.message) if not networks['networks']: return None return networks['networks'][0]['id'] -def _get_port_id(quantum, module, router_id, network_id): +def _get_port_id(neutron, module, router_id, network_id): kwargs = { 'device_id': router_id, 'network_id': network_id, } try: - ports = quantum.list_ports(**kwargs) + ports = neutron.list_ports(**kwargs) except Exception as e: module.fail_json( msg = "Error in listing ports: %s" % e.message) if not ports['ports']: return None return ports['ports'][0]['id'] -def _add_gateway_router(quantum, module, router_id, network_id): +def _add_gateway_router(neutron, module, router_id, network_id): kwargs = { 'network_id': network_id } try: - quantum.add_gateway_router(router_id, kwargs) + neutron.add_gateway_router(router_id, kwargs) except Exception as e: module.fail_json(msg = "Error in adding gateway to router: %s" % e.message) return True - -def _remove_gateway_router(quantum, module, router_id): + +def _remove_gateway_router(neutron, module, router_id): try: - quantum.remove_gateway_router(router_id) + neutron.remove_gateway_router(router_id) except Exception as e: module.fail_json(msg = "Error in removing gateway to router: %s" % e.message) return True - + def main(): - + module = AnsibleModule( argument_spec = dict( login_username = dict(default='admin'), @@ -183,29 +186,29 @@ def main(): state = dict(default='present', choices=['absent', 'present']), ), ) - - quantum = _get_quantum_client(module, module.params) - router_id = _get_router_id(module, quantum) + + neutron = _get_neutron_client(module, module.params) + router_id = _get_router_id(module, neutron) if not router_id: module.fail_json(msg="failed to get the router id, please check the router name") - network_id = _get_net_id(quantum, module) + network_id = _get_net_id(neutron, module) if not network_id: module.fail_json(msg="failed to get the network id, please check the network name and make sure it is external") - + if module.params['state'] == 'present': - port_id = _get_port_id(quantum, module, router_id, network_id) + port_id = _get_port_id(neutron, module, router_id, network_id) if not port_id: - _add_gateway_router(quantum, module, router_id, network_id) + _add_gateway_router(neutron, module, router_id, network_id) module.exit_json(changed=True, result="created") module.exit_json(changed=False, result="success") if module.params['state'] == 'absent': - port_id = _get_port_id(quantum, module, router_id, network_id) + port_id = _get_port_id(neutron, module, router_id, network_id) if not port_id: module.exit_json(changed=False, result="Success") - _remove_gateway_router(quantum, module, router_id) + _remove_gateway_router(neutron, module, router_id) module.exit_json(changed=True, result="Deleted") # this is magic, see lib/ansible/module.params['common.py diff --git a/library/cloud/quantum_router_interface b/library/cloud/quantum_router_interface index f34aecacf5..05f1f303a8 100644 --- a/library/cloud/quantum_router_interface +++ b/library/cloud/quantum_router_interface @@ -17,10 +17,13 @@ # along with this software. If not, see <http://www.gnu.org/licenses/>. try: - from quantumclient.quantum import client + try: + from neutronclient.neutron import client + except ImportError: + from quantumclient.quantum import client from keystoneclient.v2_0 import client as ksclient except ImportError: - print("failed=True msg='quantumclient and keystone client are required'") + print("failed=True msg='quantumclient (or neutronclient) and keystone client are required'") DOCUMENTATION = ''' --- module: quantum_router_interface @@ -81,7 +84,7 @@ EXAMPLES = ''' # Attach tenant1's subnet to the external router - quantum_router_interface: state=present login_username=admin login_password=admin - login_tenant_name=admin + login_tenant_name=admin tenant_name=tenant1 router_name=external_route subnet_name=t1subnet @@ -97,21 +100,21 @@ def _get_ksclient(module, kwargs): password=kwargs.get('login_password'), tenant_name=kwargs.get('login_tenant_name'), auth_url=kwargs.get('auth_url')) - except Exception as e: + except Exception as e: module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message) - global _os_keystone + global _os_keystone _os_keystone = kclient - return kclient - + return kclient + def _get_endpoint(module, ksclient): try: endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') except Exception as e: - module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + module.fail_json(msg = "Error getting network endpoint: %s" % e.message) return endpoint -def _get_quantum_client(module, kwargs): +def _get_neutron_client(module, kwargs): _ksclient = _get_ksclient(module, kwargs) token = _ksclient.auth_token endpoint = _get_endpoint(module, _ksclient) @@ -120,10 +123,10 @@ def _get_quantum_client(module, kwargs): 'endpoint_url': endpoint } try: - quantum = client.Client('2.0', **kwargs) + neutron = client.Client('2.0', **kwargs) except Exception as e: - module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) - return quantum + module.fail_json(msg = "Error in connecting to neutron: %s " % e.message) + return neutron def _set_tenant_id(module): global _os_tenant_id @@ -140,12 +143,12 @@ def _set_tenant_id(module): module.fail_json(msg = "The tenant id cannot be found, please check the paramters") -def _get_router_id(module, quantum): +def _get_router_id(module, neutron): kwargs = { 'name': module.params['router_name'], } try: - routers = quantum.list_routers(**kwargs) + routers = neutron.list_routers(**kwargs) except Exception as e: module.fail_json(msg = "Error in getting the router list: %s " % e.message) if not routers['routers']: @@ -153,27 +156,27 @@ def _get_router_id(module, quantum): return routers['routers'][0]['id'] -def _get_subnet_id(module, quantum): +def _get_subnet_id(module, neutron): subnet_id = None kwargs = { 'tenant_id': _os_tenant_id, 'name': module.params['subnet_name'], } try: - subnets = quantum.list_subnets(**kwargs) + subnets = neutron.list_subnets(**kwargs) except Exception as e: module.fail_json( msg = " Error in getting the subnet list:%s " % e.message) if not subnets['subnets']: return None return subnets['subnets'][0]['id'] - -def _get_port_id(quantum, module, router_id, subnet_id): + +def _get_port_id(neutron, module, router_id, subnet_id): kwargs = { 'tenant_id': _os_tenant_id, 'device_id': router_id, } try: - ports = quantum.list_ports(**kwargs) + ports = neutron.list_ports(**kwargs) except Exception as e: module.fail_json( msg = "Error in listing ports: %s" % e.message) if not ports['ports']: @@ -184,26 +187,26 @@ def _get_port_id(quantum, module, router_id, subnet_id): return port['id'] return None -def _add_interface_router(quantum, module, router_id, subnet_id): +def _add_interface_router(neutron, module, router_id, subnet_id): kwargs = { 'subnet_id': subnet_id } try: - quantum.add_interface_router(router_id, kwargs) + neutron.add_interface_router(router_id, kwargs) except Exception as e: module.fail_json(msg = "Error in adding interface to router: %s" % e.message) return True - -def _remove_interface_router(quantum, module, router_id, subnet_id): + +def _remove_interface_router(neutron, module, router_id, subnet_id): kwargs = { 'subnet_id': subnet_id } try: - quantum.remove_interface_router(router_id, kwargs) + neutron.remove_interface_router(router_id, kwargs) except Exception as e: module.fail_json(msg="Error in removing interface from router: %s" % e.message) return True - + def main(): module = AnsibleModule( argument_spec = dict( @@ -218,32 +221,32 @@ def main(): state = dict(default='present', choices=['absent', 'present']), ), ) - - quantum = _get_quantum_client(module, module.params) + + neutron = _get_neutron_client(module, module.params) _set_tenant_id(module) - router_id = _get_router_id(module, quantum) + router_id = _get_router_id(module, neutron) if not router_id: module.fail_json(msg="failed to get the router id, please check the router name") - subnet_id = _get_subnet_id(module, quantum) + subnet_id = _get_subnet_id(module, neutron) if not subnet_id: module.fail_json(msg="failed to get the subnet id, please check the subnet name") - + if module.params['state'] == 'present': - port_id = _get_port_id(quantum, module, router_id, subnet_id) + port_id = _get_port_id(neutron, module, router_id, subnet_id) if not port_id: - _add_interface_router(quantum, module, router_id, subnet_id) + _add_interface_router(neutron, module, router_id, subnet_id) module.exit_json(changed=True, result="created", id=port_id) module.exit_json(changed=False, result="success", id=port_id) if module.params['state'] == 'absent': - port_id = _get_port_id(quantum, module, router_id, subnet_id) + port_id = _get_port_id(neutron, module, router_id, subnet_id) if not port_id: module.exit_json(changed = False, result = "Success") - _remove_interface_router(quantum, module, router_id, subnet_id) + _remove_interface_router(neutron, module, router_id, subnet_id) module.exit_json(changed=True, result="Deleted") - + # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * main() diff --git a/library/cloud/quantum_subnet b/library/cloud/quantum_subnet index 372d346f71..9d40131061 100644 --- a/library/cloud/quantum_subnet +++ b/library/cloud/quantum_subnet @@ -17,10 +17,13 @@ # along with this software. If not, see <http://www.gnu.org/licenses/>. try: - from quantumclient.quantum import client + try: + from neutronclient.neutron import client + except ImportError: + from quantumclient.quantum import client from keystoneclient.v2_0 import client as ksclient except ImportError: - print("failed=True msg='quantum and keystone client are required'") + print("failed=True msg='quantumclient (or neutronclient) and keystoneclient are required'") DOCUMENTATION = ''' --- @@ -77,7 +80,7 @@ options: default: None ip_version: description: - - The IP version of the subnet 4 or 6 + - The IP version of the subnet 4 or 6 required: false default: 4 enable_dhcp: @@ -105,7 +108,7 @@ options: - From the subnet pool the last IP that should be assigned to the virtual machines required: false default: None -requirements: ["quantum", "keystoneclient"] +requirements: ["quantumclient", "neutronclient", "keystoneclient"] ''' EXAMPLES = ''' @@ -125,21 +128,21 @@ def _get_ksclient(module, kwargs): password=kwargs.get('login_password'), tenant_name=kwargs.get('login_tenant_name'), auth_url=kwargs.get('auth_url')) - except Exception as e: + except Exception as e: module.fail_json(msg = "Error authenticating to the keystone: %s" %e.message) - global _os_keystone + global _os_keystone _os_keystone = kclient - return kclient - + return kclient + def _get_endpoint(module, ksclient): try: endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') except Exception as e: - module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + module.fail_json(msg = "Error getting network endpoint: %s" % e.message) return endpoint -def _get_quantum_client(module, kwargs): +def _get_neutron_client(module, kwargs): _ksclient = _get_ksclient(module, kwargs) token = _ksclient.auth_token endpoint = _get_endpoint(module, _ksclient) @@ -148,10 +151,10 @@ def _get_quantum_client(module, kwargs): 'endpoint_url': endpoint } try: - quantum = client.Client('2.0', **kwargs) + neutron = client.Client('2.0', **kwargs) except Exception as e: - module.fail_json(msg = " Error in connecting to quantum: %s" % e.message) - return quantum + module.fail_json(msg = " Error in connecting to neutron: %s" % e.message) + return neutron def _set_tenant_id(module): global _os_tenant_id @@ -167,24 +170,24 @@ def _set_tenant_id(module): if not _os_tenant_id: module.fail_json(msg = "The tenant id cannot be found, please check the paramters") -def _get_net_id(quantum, module): +def _get_net_id(neutron, module): kwargs = { 'tenant_id': _os_tenant_id, 'name': module.params['network_name'], } try: - networks = quantum.list_networks(**kwargs) + networks = neutron.list_networks(**kwargs) except Exception as e: - module.fail_json("Error in listing quantum networks: %s" % e.message) + module.fail_json("Error in listing neutron networks: %s" % e.message) if not networks['networks']: return None return networks['networks'][0]['id'] -def _get_subnet_id(module, quantum): +def _get_subnet_id(module, neutron): global _os_network_id subnet_id = None - _os_network_id = _get_net_id(quantum, module) + _os_network_id = _get_net_id(neutron, module) if not _os_network_id: module.fail_json(msg = "network id of network not found.") else: @@ -193,15 +196,15 @@ def _get_subnet_id(module, quantum): 'name': module.params['name'], } try: - subnets = quantum.list_subnets(**kwargs) + subnets = neutron.list_subnets(**kwargs) except Exception as e: module.fail_json( msg = " Error in getting the subnet list:%s " % e.message) if not subnets['subnets']: return None return subnets['subnets'][0]['id'] -def _create_subnet(module, quantum): - quantum.format = 'json' +def _create_subnet(module, neutron): + neutron.format = 'json' subnet = { 'name': module.params['name'], 'ip_version': module.params['ip_version'], @@ -214,7 +217,7 @@ def _create_subnet(module, quantum): } if module.params['allocation_pool_start'] and module.params['allocation_pool_end']: allocation_pools = [ - { + { 'start' : module.params['allocation_pool_start'], 'end' : module.params['allocation_pool_end'] } @@ -227,22 +230,22 @@ def _create_subnet(module, quantum): else: subnet.pop('dns_nameservers') try: - new_subnet = quantum.create_subnet(dict(subnet=subnet)) + new_subnet = neutron.create_subnet(dict(subnet=subnet)) except Exception, e: - module.fail_json(msg = "Failure in creating subnet: %s" % e.message) + module.fail_json(msg = "Failure in creating subnet: %s" % e.message) return new_subnet['subnet']['id'] - - -def _delete_subnet(module, quantum, subnet_id): + + +def _delete_subnet(module, neutron, subnet_id): try: - quantum.delete_subnet(subnet_id) + neutron.delete_subnet(subnet_id) except Exception as e: module.fail_json( msg = "Error in deleting subnet: %s" % e.message) return True - - + + def main(): - + module = AnsibleModule( argument_spec = dict( login_username = dict(default='admin'), @@ -263,23 +266,23 @@ def main(): allocation_pool_end = dict(default=None), ), ) - quantum = _get_quantum_client(module, module.params) + neutron = _get_neutron_client(module, module.params) _set_tenant_id(module) if module.params['state'] == 'present': - subnet_id = _get_subnet_id(module, quantum) + subnet_id = _get_subnet_id(module, neutron) if not subnet_id: - subnet_id = _create_subnet(module, quantum) + subnet_id = _create_subnet(module, neutron) module.exit_json(changed = True, result = "Created" , id = subnet_id) else: module.exit_json(changed = False, result = "success" , id = subnet_id) else: - subnet_id = _get_subnet_id(module, quantum) + subnet_id = _get_subnet_id(module, neutron) if not subnet_id: module.exit_json(changed = False, result = "success") else: - _delete_subnet(module, quantum, subnet_id) + _delete_subnet(module, neutron, subnet_id) module.exit_json(changed = True, result = "deleted") - + # this is magic, see lib/ansible/module.params['common.py from ansible.module_utils.basic import * main() diff --git a/library/cloud/rax b/library/cloud/rax index d67802ce1e..03e99ea2a9 100644 --- a/library/cloud/rax +++ b/library/cloud/rax @@ -26,6 +26,13 @@ options: api_key: description: - Rackspace API key (overrides I(credentials)) + auto_increment: + description: + - Whether or not to increment a single number with the name of the + created servers. Only applicable when used with the I(group) attribute + or meta key. + default: yes + version_added: 1.5 count: description: - number of instances to launch @@ -147,6 +154,26 @@ EXAMPLES = ''' networks: - private - public + register: rax + +- name: Build an exact count of cloud servers with incremented names + hosts: local + gather_facts: False + tasks: + - name: Server build requests + local_action: + module: rax + credentials: ~/.raxpub + name: test%03d.example.org + flavor: performance1-1 + image: ubuntu-1204-lts-precise-pangolin + state: present + count: 10 + count_offset: 10 + exact_count: yes + group: test + wait: yes + register: rax ''' import sys @@ -199,7 +226,7 @@ def create(module, names, flavor, image, meta, key_name, files, lpath = os.path.expanduser(files[rpath]) try: fileobj = open(lpath, 'r') - files[rpath] = fileobj + files[rpath] = fileobj.read() except Exception, e: module.fail_json(msg='Failed to load %s' % lpath) try: @@ -347,7 +374,8 @@ def delete(module, instance_ids, wait, wait_timeout): def cloudservers(module, state, name, flavor, image, meta, key_name, files, wait, wait_timeout, disk_config, count, group, - instance_ids, exact_count, networks, count_offset): + instance_ids, exact_count, networks, count_offset, + auto_increment): cs = pyrax.cloudservers cnw = pyrax.cloud_networks servers = [] @@ -358,6 +386,15 @@ def cloudservers(module, state, name, flavor, image, meta, key_name, files, elif 'group' in meta and group is None: group = meta['group'] + # When using state=absent with group, the absent block won't match the + # names properly. Use the exact_count functionality to decrease the count + # to the desired level + was_absent = False + if group is not None and state == 'absent': + exact_count = True + state = 'present' + was_absent = True + # Check if the provided image is a UUID and if not, search for an # appropriate image using human_id and name if image: @@ -416,27 +453,43 @@ def cloudservers(module, state, name, flavor, image, meta, key_name, files, module.fail_json(msg='"group" must be provided when using ' '"exact_count"') else: - numbers = set() + if auto_increment: + numbers = set() - try: - name % 0 - except TypeError, e: - if e.message.startswith('not all'): - name = '%s%%d' % name + try: + name % 0 + except TypeError, e: + if e.message.startswith('not all'): + name = '%s%%d' % name + else: + module.fail_json(msg=e.message) + + pattern = re.sub(r'%\d+[sd]', r'(\d+)', name) + for server in cs.servers.list(): + if server.metadata.get('group') == group: + servers.append(server) + match = re.search(pattern, server.name) + if match: + number = int(match.group(1)) + numbers.add(number) + + number_range = xrange(count_offset, count_offset + count) + available_numbers = list(set(number_range) + .difference(numbers)) + else: + for server in cs.servers.list(): + if server.metadata.get('group') == group: + servers.append(server) + + # If state was absent but the count was changed, + # assume we only wanted to remove that number of instances + if was_absent: + diff = len(servers) - count + if diff < 0: + count = 0 else: - module.fail_json(msg=e.message) + count = diff - pattern = re.sub(r'%\d+[sd]', r'(\d+)', name) - for server in cs.servers.list(): - if server.metadata.get('group') == group: - servers.append(server) - match = re.search(pattern, server.name) - if match: - number = int(match.group(1)) - numbers.add(number) - - number_range = xrange(count_offset, count_offset + count) - available_numbers = list(set(number_range).difference(numbers)) if len(servers) > count: state = 'absent' del servers[:count] @@ -445,45 +498,52 @@ def cloudservers(module, state, name, flavor, image, meta, key_name, files, instance_ids.append(server.id) delete(module, instance_ids, wait, wait_timeout) elif len(servers) < count: - names = [] - numbers_to_use = available_numbers[:count - len(servers)] - for number in numbers_to_use: - names.append(name % number) + if auto_increment: + names = [] + name_slice = count - len(servers) + numbers_to_use = available_numbers[:name_slice] + for number in numbers_to_use: + names.append(name % number) + else: + names = [name] * (count - len(servers)) else: module.exit_json(changed=False, action=None, instances=[], success=[], error=[], timeout=[], instance_ids={'instances': [], 'success': [], 'error': [], 'timeout': []}) - else: if group is not None: - numbers = set() + if auto_increment: + numbers = set() - try: - name % 0 - except TypeError, e: - if e.message.startswith('not all'): - name = '%s%%d' % name - else: - module.fail_json(msg=e.message) + try: + name % 0 + except TypeError, e: + if e.message.startswith('not all'): + name = '%s%%d' % name + else: + module.fail_json(msg=e.message) - pattern = re.sub(r'%\d+[sd]', r'(\d+)', name) - for server in cs.servers.list(): - if server.metadata.get('group') == group: - servers.append(server) - match = re.search(pattern, server.name) - if match: - number = int(match.group(1)) - numbers.add(number) + pattern = re.sub(r'%\d+[sd]', r'(\d+)', name) + for server in cs.servers.list(): + if server.metadata.get('group') == group: + servers.append(server) + match = re.search(pattern, server.name) + if match: + number = int(match.group(1)) + numbers.add(number) - number_range = xrange(count_offset, - count_offset + count + len(numbers)) - available_numbers = list(set(number_range).difference(numbers)) - names = [] - numbers_to_use = available_numbers[:count] - for number in numbers_to_use: - names.append(name % number) + number_range = xrange(count_offset, + count_offset + count + len(numbers)) + available_numbers = list(set(number_range) + .difference(numbers)) + names = [] + numbers_to_use = available_numbers[:count] + for number in numbers_to_use: + names.append(name % number) + else: + names = [name] * count else: search_opts = { 'name': name, @@ -552,6 +612,7 @@ def main(): argument_spec = rax_argument_spec() argument_spec.update( dict( + auto_increment=dict(choices=BOOLEANS, default=True, type='bool'), count=dict(default=1, type='int'), count_offset=dict(default=1, type='int'), disk_config=dict(default='auto', choices=['auto', 'manual']), @@ -584,6 +645,7 @@ def main(): 'please remove "service: cloudservers" from your ' 'playbook pertaining to the "rax" module') + auto_increment = module.params.get('auto_increment') count = module.params.get('count') count_offset = module.params.get('count_offset') disk_config = module.params.get('disk_config').upper() @@ -605,7 +667,8 @@ def main(): cloudservers(module, state, name, flavor, image, meta, key_name, files, wait, wait_timeout, disk_config, count, group, - instance_ids, exact_count, networks, count_offset) + instance_ids, exact_count, networks, count_offset, + auto_increment) # import module snippets diff --git a/library/cloud/rax_clb b/library/cloud/rax_clb index 65435a42be..56d7f62d78 100644 --- a/library/cloud/rax_clb +++ b/library/cloud/rax_clb @@ -118,7 +118,6 @@ EXAMPLES = ''' ''' import sys -import os from types import NoneType @@ -136,6 +135,12 @@ PROTOCOLS = ['DNS_TCP', 'DNS_UDP', 'FTP', 'HTTP', 'HTTPS', 'IMAPS', 'IMAPv4', 'TCP_CLIENT_FIRST', 'UDP', 'UDP_STREAM', 'SFTP'] +def node_to_dict(obj): + node = obj.to_dict() + node['id'] = obj.id + return node + + def to_dict(obj): instance = {} for key in dir(obj): @@ -151,7 +156,7 @@ def to_dict(obj): elif key == 'nodes': instance[key] = [] for node in value: - instance[key].append(node.to_dict()) + instance[key].append(node_to_dict(node)) elif (isinstance(value, NON_CALLABLES) and not key.startswith('_')): instance[key] = value diff --git a/library/cloud/rds b/library/cloud/rds index c7e2844113..47539cb332 100644 --- a/library/cloud/rds +++ b/library/cloud/rds @@ -262,15 +262,6 @@ EXAMPLES = ''' import sys import time -AWS_REGIONS = ['ap-northeast-1', - 'ap-southeast-1', - 'ap-southeast-2', - 'eu-west-1', - 'sa-east-1', - 'us-east-1', - 'us-west-1', - 'us-west-2'] - try: import boto.rds except ImportError: @@ -346,25 +337,7 @@ def main(): apply_immediately = module.params.get('apply_immediately') new_instance_name = module.params.get('new_instance_name') - # allow environment variables to be used if ansible vars aren't set - if not region: - if 'AWS_REGION' in os.environ: - region = os.environ['AWS_REGION'] - elif 'EC2_REGION' in os.environ: - region = os.environ['EC2_REGION'] - - if not aws_secret_key: - if 'AWS_SECRET_KEY' in os.environ: - aws_secret_key = os.environ['AWS_SECRET_KEY'] - elif 'EC2_SECRET_KEY' in os.environ: - aws_secret_key = os.environ['EC2_SECRET_KEY'] - - if not aws_access_key: - if 'AWS_ACCESS_KEY' in os.environ: - aws_access_key = os.environ['AWS_ACCESS_KEY'] - elif 'EC2_ACCESS_KEY' in os.environ: - aws_access_key = os.environ['EC2_ACCESS_KEY'] - + ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module) if not region: module.fail_json(msg = str("region not specified and unable to determine region from EC2_REGION.")) @@ -577,5 +550,6 @@ def main(): # import module snippets from ansible.module_utils.basic import * +from ansible.module_utils.ec2 import * main() diff --git a/library/commands/shell b/library/commands/shell index 5b3969a1fb..03299b967c 100644 --- a/library/commands/shell +++ b/library/commands/shell @@ -7,20 +7,26 @@ DOCUMENTATION = ''' module: shell short_description: Execute commands in nodes. description: - - The shell module takes the command name followed by a list of arguments, - space delimited. It is almost exactly like the M(command) module but runs + - The M(shell) module takes the command name followed by a list of space-delimited arguments. + It is almost exactly like the M(command) module but runs the command through a shell (C(/bin/sh)) on the remote node. version_added: "0.2" options: - (free form): + free_form: description: - - The command module takes a free form command to run - required: null + - The shell module takes a free form command to run + required: true default: null creates: description: - - a filename, when it already exists, this step will NOT be run - required: false + - a filename, when it already exists, this step will B(not) be run. + required: no + default: null + removes: + description: + - a filename, when it does not exist, this step will B(not) be run. + version_added: "0.8" + required: no default: null chdir: description: diff --git a/library/database/mysql_db b/library/database/mysql_db index f949cced30..cf98701115 100644 --- a/library/database/mysql_db +++ b/library/database/mysql_db @@ -116,13 +116,13 @@ def db_delete(cursor, db): cursor.execute(query) return True -def db_dump(module, host, user, password, db_name, target, socket=None): +def db_dump(module, host, user, password, db_name, target, port, socket=None): cmd = module.get_bin_path('mysqldump', True) - cmd += " --quick --user=%s --password=%s" %(user, password) + cmd += " --quick --user=%s --password='%s'" %(user, password) if socket is not None: cmd += " --socket=%s" % socket else: - cmd += " --host=%s" % host + cmd += " --host=%s --port=%s" % (host, port) cmd += " %s" % db_name if os.path.splitext(target)[-1] == '.gz': cmd = cmd + ' | gzip > ' + target @@ -133,13 +133,13 @@ def db_dump(module, host, user, password, db_name, target, socket=None): rc, stdout, stderr = module.run_command(cmd) return rc, stdout, stderr -def db_import(module, host, user, password, db_name, target, socket=None): +def db_import(module, host, user, password, db_name, target, port, socket=None): cmd = module.get_bin_path('mysql', True) - cmd += " --user=%s --password=%s" %(user, password) + cmd += " --user=%s --password='%s'" %(user, password) if socket is not None: cmd += " --socket=%s" % socket else: - cmd += " --host=%s" % host + cmd += " --host=%s --port=%s" % (host, port) cmd += " -D %s" % db_name if os.path.splitext(target)[-1] == '.gz': cmd = 'gunzip < ' + target + ' | ' + cmd @@ -282,6 +282,7 @@ def main(): elif state == "dump": rc, stdout, stderr = db_dump(module, login_host, login_user, login_password, db, target, + port=module.params['login_port'], socket=module.params['login_unix_socket']) if rc != 0: module.fail_json(msg="%s" % stderr) @@ -290,6 +291,7 @@ def main(): elif state == "import": rc, stdout, stderr = db_import(module, login_host, login_user, login_password, db, target, + port=module.params['login_port'], socket=module.params['login_unix_socket']) if rc != 0: module.fail_json(msg="%s" % stderr) diff --git a/library/database/mysql_user b/library/database/mysql_user index 1eeb81f112..e7fad3d77c 100644 --- a/library/database/mysql_user +++ b/library/database/mysql_user @@ -114,6 +114,9 @@ EXAMPLES = """ # Create database user with name 'bob' and password '12345' with all database privileges - mysql_user: name=bob password=12345 priv=*.*:ALL state=present +# Creates database user 'bob' and password '12345' with all database privileges and 'WITH GRANT OPTION' +- mysql_user: name=bob password=12345 priv=*.*:ALL,GRANT state=present + # Ensure no user named 'sally' exists, also passing in the auth credentials. - mysql_user: login_user=root login_password=123456 name=sally state=absent diff --git a/library/files/file b/library/files/file index 8dcead1b40..bef175873e 100644 --- a/library/files/file +++ b/library/files/file @@ -124,7 +124,7 @@ options: choices: [ "yes", "no" ] description: - 'force the creation of the symlinks in two cases: the source file does - not exist (but will appear later); the destination exists and a file (so, we need to unlink the + not exist (but will appear later); the destination exists and is a file (so, we need to unlink the "path" file and create symlink to the "src" file in place of it).' notes: - See also M(copy), M(template), M(assemble) @@ -245,7 +245,7 @@ def main(): module.exit_json(path=path, changed=True) if prev_state != 'absent' and prev_state != state: - if not (force and (prev_state == 'file' or prev_state == 'directory') and state == 'link') and state != 'touch': + if not (force and (prev_state == 'file' or prev_state == 'hard' or prev_state == 'directory') and state == 'link') and state != 'touch': module.fail_json(path=path, msg='refusing to convert between %s and %s for %s' % (prev_state, state, src)) if prev_state == 'absent' and state == 'absent': @@ -307,6 +307,10 @@ def main(): if not force: module.fail_json(dest=path, src=src, msg='Cannot link, file exists at destination') changed = True + elif prev_state == 'directory': + if not force: + module.fail_json(dest=path, src=src, msg='Cannot link, directory exists at destination') + changed = True else: module.fail_json(dest=path, src=src, msg='unexpected position reached') diff --git a/library/files/template b/library/files/template index 35ac831e18..5f64b6d9e6 100644 --- a/library/files/template +++ b/library/files/template @@ -47,9 +47,12 @@ options: - all arguments accepted by the M(file) module also work here required: false notes: - - Since Ansible version 0.9, templates are loaded with C(trim_blocks=True). - - 'You can override jinja2 settings by adding a special header to template file. - i.e. C(#jinja2: trim_blocks: False)' + - "Since Ansible version 0.9, templates are loaded with C(trim_blocks=True)." + + - "Also, you can override jinja2 settings by adding a special header to template file. + i.e. C(#jinja2:variable_start_string:'[%' , variable_end_string:'%]') + which changes the variable interpolation markers to [% var %] instead of {{ var }}. This is the best way to prevent evaluation of things that look like, but should not be Jinja2. raw/endraw in Jinja2 will not work as you expect because templates in Ansible are recursively evaluated." + requirements: [] author: Michael DeHaan ''' diff --git a/library/net_infrastructure/bigip_monitor_http b/library/net_infrastructure/bigip_monitor_http index 924c826eaa..7a05808e74 100644 --- a/library/net_infrastructure/bigip_monitor_http +++ b/library/net_infrastructure/bigip_monitor_http @@ -94,19 +94,19 @@ options: required: true default: none ip: - description: + description: - IP address part of the ipport definition. The default API setting is "0.0.0.0". required: false default: none port: - description: + description: - port address part op the ipport definition. Tyhe default API setting is 0. required: false default: none interval: - description: + description: - The interval specifying how frequently the monitor instance of this template will run. By default, this interval is used for up and down states. The default API setting is 5. @@ -199,7 +199,7 @@ def check_monitor_exists(module, api, monitor, parent): def create_monitor(api, monitor, template_attributes): - try: + try: api.LocalLB.Monitor.create_template(templates=[{'template_name': monitor, 'template_type': TEMPLATE_TYPE}], template_attributes=[template_attributes]) except bigsuds.OperationFailed, e: if "already exists" in str(e): @@ -282,7 +282,7 @@ def set_ipport(api, monitor, ipport): # =========================================== # main loop # -# writing a module for other monitor types should +# writing a module for other monitor types should # only need an updated main() (and monitor specific functions) def main(): @@ -345,19 +345,19 @@ def main(): if port is None: port = cur_ipport['ipport']['port'] else: # use API defaults if not defined to create it - if interval is None: + if interval is None: interval = 5 - if timeout is None: + if timeout is None: timeout = 16 - if ip is None: + if ip is None: ip = '0.0.0.0' - if port is None: + if port is None: port = 0 - if send is None: + if send is None: send = '' - if receive is None: + if receive is None: receive = '' - if receive_disable is None: + if receive_disable is None: receive_disable = '' # define and set address type @@ -394,7 +394,7 @@ def main(): {'type': 'ITYPE_TIMEOUT', 'value': timeout}, {'type': 'ITYPE_TIME_UNTIL_UP', - 'value': interval}] + 'value': time_until_up}] # main logic, monitor generic @@ -405,7 +405,7 @@ def main(): if state == 'absent': if monitor_exists: if not module.check_mode: - # possible race condition if same task + # possible race condition if same task # on other node deleted it first result['changed'] |= delete_monitor(api, monitor) else: @@ -414,26 +414,24 @@ def main(): else: # state present ## check for monitor itself if not monitor_exists: # create it - if not module.check_mode: + if not module.check_mode: # again, check changed status here b/c race conditions # if other task already created it result['changed'] |= create_monitor(api, monitor, template_attributes) - else: + else: result['changed'] |= True ## check for monitor parameters # whether it already existed, or was just created, now update # the update functions need to check for check mode but # cannot update settings if it doesn't exist which happens in check mode - if monitor_exists and not module.check_mode: - result['changed'] |= update_monitor_properties(api, module, monitor, - template_string_properties, - template_integer_properties) - # else assume nothing changed + result['changed'] |= update_monitor_properties(api, module, monitor, + template_string_properties, + template_integer_properties) # we just have to update the ipport if monitor already exists and it's different if monitor_exists and cur_ipport != ipport: - set_ipport(api, monitor, ipport) + set_ipport(api, monitor, ipport) result['changed'] |= True #else: monitor doesn't exist (check mode) or ipport is already ok diff --git a/library/net_infrastructure/openvswitch_bridge b/library/net_infrastructure/openvswitch_bridge index 9e8d521d39..4b05f4079f 100644 --- a/library/net_infrastructure/openvswitch_bridge +++ b/library/net_infrastructure/openvswitch_bridge @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/python #coding: utf-8 -*- # This module is free software: you can redistribute it and/or modify diff --git a/library/net_infrastructure/openvswitch_port b/library/net_infrastructure/openvswitch_port index a33946e9a1..00684496b4 100644 --- a/library/net_infrastructure/openvswitch_port +++ b/library/net_infrastructure/openvswitch_port @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/python #coding: utf-8 -*- # This module is free software: you can redistribute it and/or modify diff --git a/library/packaging/apt b/library/packaging/apt index 409eb898e6..eb64f8701f 100644 --- a/library/packaging/apt +++ b/library/packaging/apt @@ -324,7 +324,10 @@ def upgrade(m, mode="yes", force=False, upgrade_command = "safe-upgrade" if force: - force_yes = '--force-yes' + if apt_cmd == APT_GET_CMD: + force_yes = '--force-yes' + else: + force_yes = '' else: force_yes = '' diff --git a/library/packaging/apt_key b/library/packaging/apt_key index dee73762c6..eee8633702 100644 --- a/library/packaging/apt_key +++ b/library/packaging/apt_key @@ -124,7 +124,7 @@ def all_keys(module, keyring): return results def key_present(module, key_id): - (rc, out, err) = module.run_command("apt-key list | 2>&1 grep -q %s" % key_id) + (rc, out, err) = module.run_command("apt-key list | 2>&1 grep -i -q %s" % key_id) return rc == 0 def download_key(module, url): diff --git a/library/packaging/apt_repository b/library/packaging/apt_repository index 9965bc22a3..26b18ec4bc 100644 --- a/library/packaging/apt_repository +++ b/library/packaging/apt_repository @@ -3,6 +3,7 @@ # (c) 2012, Matt Wright <matt@nobien.net> # (c) 2013, Alexander Saltanov <asd@mokote.com> +# (c) 2014, Rutger Spiertz <rutger@kumina.nl> # # This file is part of Ansible # @@ -111,8 +112,9 @@ class SourcesList(object): self.files = {} # group sources by file self.default_file = apt_pkg.config.find_file('Dir::Etc::sourcelist') - # read sources.list - self.load(self.default_file) + # read sources.list if it exists + if os.path.isfile(self.default_file): + self.load(self.default_file) # read sources.list.d for file in glob.iglob('%s/*.list' % apt_pkg.config.find_dir('Dir::Etc::sourceparts')): diff --git a/library/packaging/easy_install b/library/packaging/easy_install index eae361c432..bdacf8e464 100644 --- a/library/packaging/easy_install +++ b/library/packaging/easy_install @@ -135,7 +135,6 @@ def main(): name = module.params['name'] env = module.params['virtualenv'] executable = module.params['executable'] - easy_install = _get_easy_install(module, env, executable) site_packages = module.params['virtualenv_site_packages'] virtualenv_command = module.params['virtualenv_command'] @@ -159,6 +158,8 @@ def main(): out += out_venv err += err_venv + easy_install = _get_easy_install(module, env, executable) + cmd = None changed = False installed = _is_package_installed(module, name, easy_install) diff --git a/library/packaging/homebrew b/library/packaging/homebrew index 8801a4e6c4..ab1362acf1 100644 --- a/library/packaging/homebrew +++ b/library/packaging/homebrew @@ -71,7 +71,7 @@ def query_package(module, brew_path, name, state="present"): """ Returns whether a package is installed or not. """ if state == "present": - rc, out, err = module.run_command("%s list -m1 | grep -q '^%s$'" % (brew_path, name)) + rc, out, err = module.run_command("%s list %s" % (brew_path, name)) if rc == 0: return True diff --git a/library/packaging/npm b/library/packaging/npm index 0cdcf64c63..3a4cd13f5d 100644 --- a/library/packaging/npm +++ b/library/packaging/npm @@ -30,7 +30,7 @@ options: name: description: - The name of a node.js library to install - requires: false + required: false path: description: - The base path where to install the node.js libraries @@ -101,7 +101,7 @@ class Npm(object): self.version = kwargs['version'] self.path = kwargs['path'] self.production = kwargs['production'] - + if kwargs['executable']: self.executable = kwargs['executable'] else: @@ -119,7 +119,7 @@ class Npm(object): if self.glbl: cmd.append('--global') if self.production: - cmd.append('--production') + cmd.append('--production') if self.name: cmd.append(self.name_version) @@ -180,7 +180,7 @@ def main(): executable=dict(default=None), state=dict(default='present', choices=['present', 'absent', 'latest']) ) - arg_spec['global']=dict(default='no', type='bool') + arg_spec['global'] = dict(default='no', type='bool') module = AnsibleModule( argument_spec=arg_spec, supports_check_mode=True diff --git a/library/packaging/pip b/library/packaging/pip index 13cf162fe1..56775177de 100644 --- a/library/packaging/pip +++ b/library/packaging/pip @@ -147,9 +147,8 @@ EXAMPLES = ''' def _get_cmd_options(module, cmd): thiscmd = cmd + " --help" rc, stdout, stderr = module.run_command(thiscmd) - #import epdb; epdb.serve() if rc != 0: - module.fail_json(msg="Could not get --help output from %s" % virtualenv) + module.fail_json(msg="Could not get output from %s: %s" % (thiscmd, stdout + stderr)) words = stdout.strip().split() cmd_options = [ x for x in words if x.startswith('--') ] @@ -275,6 +274,7 @@ def main(): pip = _get_pip(module, env, module.params['executable']) cmd = '%s %s' % (pip, state_map[state]) + cmd_opts = None # If there's a virtualenv we want things we install to be able to use other # installations that exist as binaries within this virtualenv. Example: we @@ -319,7 +319,11 @@ def main(): is_local_path = True # for tarball or vcs source, applying --use-mirrors doesn't really make sense is_package = is_vcs or is_tar or is_local_path # just a shortcut for bool - if not is_package and state != 'absent' and use_mirrors: + + if cmd_opts is None: + cmd_opts = _get_cmd_options(module, '%s %s' % (pip, state_map[state])) + + if not is_package and state != 'absent' and use_mirrors and '--use-mirrors' in cmd_opts: cmd += ' --use-mirrors' cmd += ' %s' % _get_full_name(name, version) elif requirements: diff --git a/library/packaging/rpm_key b/library/packaging/rpm_key index a1c4b036a6..8253247734 100644 --- a/library/packaging/rpm_key +++ b/library/packaging/rpm_key @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python # -*- coding: utf-8 -*- """ diff --git a/library/packaging/yum b/library/packaging/yum index 4c930f2f77..744a876a04 100644 --- a/library/packaging/yum +++ b/library/packaging/yum @@ -31,7 +31,7 @@ module: yum version_added: historical short_description: Manages packages with the I(yum) package manager description: - - Will install, upgrade, remove, and list packages with the I(yum) package manager. + - Installs, upgrade, removes, and lists packages and groups with the I(yum) package manager. options: name: description: @@ -41,7 +41,7 @@ options: aliases: [] list: description: - - Various non-idempotent commands for usage with C(/usr/bin/ansible) and I(not) playbooks. See examples. + - Various (non-idempotent) commands for usage with C(/usr/bin/ansible) and I(not) playbooks. See examples. required: false default: null state: @@ -94,13 +94,26 @@ author: Seth Vidal ''' EXAMPLES = ''' -- yum: name=httpd state=latest -- yum: name=httpd state=removed -- yum: name=httpd enablerepo=testing state=installed -- yum: name=* state=latest -- yum: name=http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present -- yum: name=/usr/local/src/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present -- yum: name="@Development tools" state=present +- name: install the latest version of Apache + yum: name=httpd state=latest + +- name: remove the Apache package + yum: name=httpd state=removed + +- name: install the latest version of Apche from the testing repo + yum: name=httpd enablerepo=testing state=installed + +- name: upgrade all packages + yum: name=* state=latest + +- name: install the nginx rpm from a remote repo + yum: name=http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present + +- name: install nginx rpm from a local file + yum: name=/usr/local/src/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present + +- name: install the 'Development tools' package group + yum: name="@Development tools" state=present ''' def_qf = "%{name}-%{version}-%{release}.%{arch}" diff --git a/library/packaging/zypper_repository b/library/packaging/zypper_repository index f585d0bde2..2dc177dc7b 100644 --- a/library/packaging/zypper_repository +++ b/library/packaging/zypper_repository @@ -120,7 +120,7 @@ def main(): description=dict(required=False), disable_gpg_check = dict(required=False, default='no', type='bool'), ), - supports_check_mode=True, + supports_check_mode=False, ) repo = module.params['repo'] diff --git a/library/source_control/git b/library/source_control/git index 61ef24e148..41cf53133d 100644 --- a/library/source_control/git +++ b/library/source_control/git @@ -43,6 +43,12 @@ options: - What version of the repository to check out. This can be the full 40-character I(SHA-1) hash, the literal string C(HEAD), a branch name, or a tag name. + accept_hostkey: + required: false + default: false + version_added: "1.5" + description: + - Add the hostkey for the repo url if not already added. reference: required: false default: null @@ -118,6 +124,7 @@ EXAMPLES = ''' import re import tempfile + def get_version(git_path, dest, ref="HEAD"): ''' samples the version of the git repo ''' os.chdir(dest) @@ -352,6 +359,7 @@ def main(): force=dict(default='yes', type='bool'), depth=dict(default=None, type='int'), update=dict(default='yes', type='bool'), + accept_hostkey=dict(default='no', type='bool'), executable=dict(default=None), bare=dict(default='no', type='bool'), ), @@ -369,6 +377,10 @@ def main(): reference = module.params['reference'] git_path = module.params['executable'] or module.get_bin_path('git', True) + # add the git repo's hostkey + #if module.params['accept_hostkey']: + add_git_host_key(module, repo, accept_hostkey=module.params['accept_hostkey']) + if bare: gitconfig = os.path.join(dest, 'config') else: @@ -430,4 +442,6 @@ def main(): # import module snippets from ansible.module_utils.basic import * +from ansible.module_utils.known_hosts import * + main() diff --git a/library/source_control/hg b/library/source_control/hg index e6730ad6f5..fcaa73457a 100644 --- a/library/source_control/hg +++ b/library/source_control/hg @@ -59,9 +59,7 @@ options: choices: [ "yes", "no" ] purge: description: - - Deletes untracked files. Runs C(hg purge). Note this requires C(purge) extension to - be enabled if C(purge=yes). This module will modify hgrc file on behalf of the user - and undo the changes before exiting the task. + - Deletes untracked files. Runs C(hg purge). required: false default: "no" choices: [ "yes", "no" ] @@ -85,36 +83,6 @@ EXAMPLES = ''' - hg: repo=https://bitbucket.org/user/repo1 dest=/home/user/repo1 revision=stable purge=yes ''' -def _set_hgrc(hgrc, vals): - parser = ConfigParser.SafeConfigParser() - parser.read(hgrc) - - # val is a list of triple-tuple of the form [(section, option, value),...] - for each in vals: - (section, option, value) = each - if not parser.has_section(section): - parser.add_section(section) - parser.set(section, option, value) - - f = open(hgrc, 'w') - parser.write(f) - f.close() - - -def _undo_hgrc(hgrc, vals): - parser = ConfigParser.SafeConfigParser() - parser.read(hgrc) - - for each in vals: - (section, option, value) = each - if parser.has_section(section): - parser.remove_option(section, option) - - f = open(hgrc, 'w') - parser.write(f) - f.close() - - class Hg(object): def __init__(self, module, dest, repo, revision, hg_path): @@ -129,7 +97,8 @@ class Hg(object): return (rc, out, err) def _list_untracked(self): - return self._command(['purge', '-R', self.dest, '--print']) + args = ['purge', '--config', 'extensions.purge=', '-R', self.dest, '--print'] + return self._command(args) def get_revision(self): """ @@ -168,10 +137,6 @@ class Hg(object): return True def purge(self): - hgrc = os.path.join(self.dest, '.hg/hgrc') - purge_option = [('extensions', 'purge', '')] - _set_hgrc(hgrc, purge_option) # enable purge extension - # before purge, find out if there are any untracked files (rc1, out1, err1) = self._list_untracked() if rc1 != 0: @@ -179,10 +144,9 @@ class Hg(object): # there are some untrackd files if out1 != '': - (rc2, out2, err2) = self._command(['purge', '-R', self.dest]) - if rc2 == 0: - _undo_hgrc(hgrc, purge_option) - else: + args = ['purge', '--config', 'extensions.purge=', '-R', self.dest] + (rc2, out2, err2) = self._command(args) + if rc2 != 0: self.module.fail_json(msg=err2) return True else: diff --git a/library/source_control/subversion b/library/source_control/subversion index 38417e801b..0bb2560553 100644 --- a/library/source_control/subversion +++ b/library/source_control/subversion @@ -94,6 +94,7 @@ class Subversion(object): def _exec(self, args): bits = [ + 'LANG=C', self.svn_path, '--non-interactive', '--trust-server-cert', diff --git a/library/system/authorized_key b/library/system/authorized_key index 7626a9a07c..c9b178c374 100644 --- a/library/system/authorized_key +++ b/library/system/authorized_key @@ -114,6 +114,27 @@ import tempfile import re import shlex +class keydict(dict): + + """ a dictionary that maintains the order of keys as they are added """ + + # http://stackoverflow.com/questions/2328235/pythonextend-the-dict-class + + def __init__(self, *args, **kw): + super(keydict,self).__init__(*args, **kw) + self.itemlist = super(keydict,self).keys() + def __setitem__(self, key, value): + self.itemlist.append(key) + super(keydict,self).__setitem__(key, value) + def __iter__(self): + return iter(self.itemlist) + def keys(self): + return self.itemlist + def values(self): + return [self[key] for key in self] + def itervalues(self): + return (self[key] for key in self) + def keyfile(module, user, write=False, path=None, manage_dir=True): """ Calculate name of authorized keys file, optionally creating the @@ -176,7 +197,7 @@ def parseoptions(module, options): reads a string containing ssh-key options and returns a dictionary of those options ''' - options_dict = {} + options_dict = keydict() #ordered dict if options: token_exp = [ # matches separator @@ -246,9 +267,8 @@ def parsekey(module, raw_key): # check for options if type_index is None: return None - elif type_index == 1: - # parse the options and store them - options = key_parts[0] + elif type_index > 0: + options = " ".join(key_parts[:type_index]) # parse the options (if any) options = parseoptions(module, options) @@ -292,7 +312,7 @@ def writekeys(module, filename, keys): option_str = "" if options: option_strings = [] - for option_key in sorted(options.keys()): + for option_key in options.keys(): if options[option_key]: option_strings.append("%s=\"%s\"" % (option_key, options[option_key])) else: @@ -321,7 +341,9 @@ def enforce_state(module, params): state = params.get("state", "present") key_options = params.get("key_options", None) - key = key.split('\n') + # extract indivial keys into an array, skipping blank lines and comments + key = [s for s in key.splitlines() if s and not s.startswith('#')] + # check current state -- just get the filename, don't create file do_write = False @@ -330,10 +352,11 @@ def enforce_state(module, params): # Check our new keys, if any of them exist we'll continue. for new_key in key: - if key_options is not None: - new_key = "%s %s" % (key_options, new_key) - parsed_new_key = parsekey(module, new_key) + if key_options is not None: + parsed_options = parseoptions(module, key_options) + parsed_new_key = (parsed_new_key[0], parsed_new_key[1], parsed_options, parsed_new_key[3]) + if not parsed_new_key: module.fail_json(msg="invalid key specified: %s" % new_key) diff --git a/library/system/hostname b/library/system/hostname index 9aa7c206a4..781bdcd08a 100644 --- a/library/system/hostname +++ b/library/system/hostname @@ -146,6 +146,12 @@ class DebianStrategy(GenericStrategy): HOSTNAME_FILE = '/etc/hostname' def get_permanent_hostname(self): + if not os.path.isfile(self.HOSTNAME_FILE): + try: + open(self.HOSTNAME_FILE, "a").write("") + except IOError, err: + self.module.fail_json(msg="failed to write file: %s" % + str(err)) try: f = open(self.HOSTNAME_FILE) try: @@ -250,6 +256,11 @@ class AmazonLinuxHostname(Hostname): distribution = 'Amazon' strategy_class = RedHatStrategy +class ScientificLinuxHostname(Hostname): + platform = 'Linux' + distribution = 'Scientific' + strategy_class = RedHatStrategy + # =========================================== class FedoraStrategy(GenericStrategy): diff --git a/library/system/modprobe b/library/system/modprobe index 80ec66d9b1..82ca86b9bd 100755 --- a/library/system/modprobe +++ b/library/system/modprobe @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/python #coding: utf-8 -*- # This module is free software: you can redistribute it and/or modify diff --git a/library/system/service b/library/system/service index aac7319d75..ed5712c09f 100644 --- a/library/system/service +++ b/library/system/service @@ -431,10 +431,10 @@ class LinuxService(Service): if check_systemd(self.name): # service is managed by systemd self.enable_cmd = location['systemctl'] - elif os.path.exists("/etc/init/%s.conf" % self.name): + elif location['initctl'] and os.path.exists("/etc/init/%s.conf" % self.name): # service is managed by upstart self.enable_cmd = location['initctl'] - elif os.path.exists("/etc/init.d/%s" % self.name): + elif location['update-rc.d'] and os.path.exists("/etc/init.d/%s" % self.name): # service is managed by with SysV init scripts, but with update-rc.d self.enable_cmd = location['update-rc.d'] else: @@ -649,7 +649,7 @@ class LinuxService(Service): return if self.enable: - # make sure the init.d symlinks are created + # make sure the init.d symlinks are created # otherwise enable might not work (rc, out, err) = self.execute_command("%s %s defaults" \ % (self.enable_cmd, self.name)) diff --git a/library/system/setup b/library/system/setup index 939752f145..34d79fe9b2 100755 --- a/library/system/setup +++ b/library/system/setup @@ -83,7 +83,7 @@ ansible all -m setup -a 'filter=ansible_*_mb' # Display only facts returned by facter. ansible all -m setup -a 'filter=facter_*' -# Display only facts returned by facter. +# Display only facts about certain interfaces. ansible all -m setup -a 'filter=ansible_eth[0-2]' """ @@ -118,7 +118,8 @@ class Facts(object): '/etc/alpine-release': 'Alpine', '/etc/release': 'Solaris', '/etc/arch-release': 'Archlinux', - '/etc/SuSE-release': 'SuSE' } + '/etc/SuSE-release': 'SuSE', + '/etc/os-release': 'Debian' } SELINUX_MODE_DICT = { 1: 'enforcing', 0: 'permissive', -1: 'disabled' } # A list of dicts. If there is a platform with more than one @@ -328,6 +329,11 @@ class Facts(object): elif name == 'SuSE': data = get_file_content(path).splitlines() self.facts['distribution_release'] = data[2].split('=')[1].strip() + elif name == 'Debian': + data = get_file_content(path).split('\n')[0] + release = re.search("PRETTY_NAME.+ \(?([^ ]+?)\)?\"", data) + if release: + self.facts['distribution_release'] = release.groups()[0] else: self.facts['distribution'] = name @@ -1540,8 +1546,7 @@ class LinuxNetwork(Network): iface = words[-1] if iface != device: interfaces[iface] = {} - interfaces[iface].update(interfaces[device]) - if "ipv4_secondaries" not in interfaces[iface]: + if not secondary and "ipv4_secondaries" not in interfaces[iface]: interfaces[iface]["ipv4_secondaries"] = [] if not secondary or "ipv4" not in interfaces[iface]: interfaces[iface]['ipv4'] = {'address': address, @@ -1553,6 +1558,15 @@ class LinuxNetwork(Network): 'netmask': netmask, 'network': network, }) + + # add this secondary IP to the main device + if secondary: + interfaces[device]["ipv4_secondaries"].append({ + 'address': address, + 'netmask': netmask, + 'network': network, + }) + # If this is the default address, update default_ipv4 if 'address' in default_ipv4 and default_ipv4['address'] == address: default_ipv4['netmask'] = netmask @@ -2072,6 +2086,11 @@ class LinuxVirtual(Virtual): self.facts['virtualization_role'] = 'guest' return + if product_name == 'RHEV Hypervisor': + self.facts['virtualization_type'] = 'RHEV' + self.facts['virtualization_role'] = 'guest' + return + if product_name == 'VMware Virtual Platform': self.facts['virtualization_type'] = 'VMware' self.facts['virtualization_role'] = 'guest' diff --git a/library/system/sysctl b/library/system/sysctl index 71320a3453..3541a45aee 100644 --- a/library/system/sysctl +++ b/library/system/sysctl @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # (c) 2012, David "DaviXX" CHANIAL <david.chanial@gmail.com> +# (c) 2014, James Tanner <tanner.jc@gmail.com> # # This file is part of Ansible # @@ -41,19 +42,9 @@ options: aliases: [ 'val' ] state: description: - - Whether the entry should be present or absent. + - Whether the entry should be present or absent in the sysctl file. choices: [ "present", "absent" ] default: present - checks: - description: - - If C(none), no smart/facultative checks will be made. If - C(before), some checks are performed before any update (i.e. is - the sysctl key writable?). If C(after), some checks are performed - after an update (i.e. does kernel return the set value?). If - C(both), all of the smart checks (C(before) and C(after)) are - performed. - choices: [ "none", "before", "after", "both" ] - default: both reload: description: - If C(yes), performs a I(/sbin/sysctl -p) if the C(sysctl_file) is @@ -66,6 +57,13 @@ options: - Specifies the absolute path to C(sysctl.conf), if not C(/etc/sysctl.conf). required: false default: /etc/sysctl.conf + sysctl_set: + description: + - Verify token value with the sysctl command and set with -w if necessary + choices: [ "yes", "no" ] + required: false + version_added: 1.5 + default: False notes: [] requirements: [] author: David "DaviXX" CHANIAL <david.chanial@gmail.com> @@ -78,10 +76,14 @@ EXAMPLES = ''' # Remove kernel.panic entry from /etc/sysctl.conf - sysctl: name=kernel.panic state=absent sysctl_file=/etc/sysctl.conf -# Set kernel.panic to 3 in /tmp/test_sysctl.conf, check if the sysctl key -# seems writable, but do not reload sysctl, and do not check kernel value -# after (not needed, because the real /etc/sysctl.conf was not updated) -- sysctl: name=kernel.panic value=3 sysctl_file=/tmp/test_sysctl.conf check=before reload=no +# Set kernel.panic to 3 in /tmp/test_sysctl.conf +- sysctl: name=kernel.panic value=3 sysctl_file=/tmp/test_sysctl.conf reload=no + +# Set ip fowarding on in /proc and do not reload the sysctl file +- sysctl: name="net.ipv4.ip_forward" value=1 sysctl_set=yes + +# Set ip forwarding on in /proc and in the sysctl file and reload if necessary +- sysctl: name="net.ipv4.ip_forward" value=1 sysctl_set=yes state=present reload=yes ''' # ============================================================== @@ -90,137 +92,174 @@ import os import tempfile import re -# ============================================================== +class SysctlModule(object): -def reload_sysctl(module, **sysctl_args): - # update needed ? - if not sysctl_args['reload']: - return 0, '' + def __init__(self, module): + self.module = module + self.args = self.module.params - # do it - if get_platform().lower() == 'freebsd': - # freebsd doesn't support -p, so reload the sysctl service - rc,out,err = module.run_command('/etc/rc.d/sysctl reload') - else: - # system supports reloading via the -p flag to sysctl, so we'll use that - sysctl_cmd = module.get_bin_path('sysctl', required=True) - rc,out,err = module.run_command([sysctl_cmd, '-p', sysctl_args['sysctl_file']]) - - return rc,out+err + self.sysctl_cmd = self.module.get_bin_path('sysctl', required=True) + self.sysctl_file = self.args['sysctl_file'] -# ============================================================== + self.proc_value = None # current token value in proc fs + self.file_value = None # current token value in file + self.file_lines = [] # all lines in the file + self.file_values = {} # dict of token values -def write_sysctl(module, lines, **sysctl_args): - # open a tmp file - fd, tmp_path = tempfile.mkstemp('.conf', '.ansible_m_sysctl_', os.path.dirname(sysctl_args['sysctl_file'])) - f = open(tmp_path,"w") - try: - for l in lines: - f.write(l) - except IOError, e: - module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, str(e))) - f.flush() - f.close() + self.changed = False # will change occur + self.set_proc = False # does sysctl need to set value + self.write_file = False # does the sysctl file need to be reloaded - # replace the real one - module.atomic_move(tmp_path, sysctl_args['sysctl_file']) + self.process() - # end - return sysctl_args + # ============================================================== + # LOGIC + # ============================================================== -# ============================================================== + def process(self): -def sysctl_args_expand(**sysctl_args): - if get_platform().lower() == 'freebsd': - # FreeBSD does not use the /proc file system, and instead - # just uses the sysctl command to set the values - sysctl_args['key_path'] = None - else: - sysctl_args['key_path'] = sysctl_args['name'].replace('.' ,'/') - sysctl_args['key_path'] = '/proc/sys/' + sysctl_args['key_path'] - return sysctl_args - -# ============================================================== - -def sysctl_args_collapse(**sysctl_args): - # go ahead - if sysctl_args.get('key_path') is not None: - del sysctl_args['key_path'] - if sysctl_args['state'] == 'absent' and 'value' in sysctl_args: - del sysctl_args['value'] - - # end - return sysctl_args - -# ============================================================== - -def sysctl_check(module, current_step, **sysctl_args): - - # no smart checks at this step ? - if sysctl_args['checks'] == 'none': - return 0, '' - if current_step == 'before' and sysctl_args['checks'] not in ['before', 'both']: - return 0, '' - if current_step == 'after' and sysctl_args['checks'] not in ['after', 'both']: - return 0, '' - - # checking coherence - if sysctl_args['state'] == 'absent' and sysctl_args['value'] is not None: - return 1, 'value=x must not be supplied when state=absent' - - if sysctl_args['state'] == 'present' and sysctl_args['value'] is None: - return 1, 'value=x must be supplied when state=present' - - if not sysctl_args['reload'] and sysctl_args['checks'] in ['after', 'both']: - return 1, 'checks cannot be set to after or both if reload=no' - - if sysctl_args['key_path'] is not None: - # getting file stat - if not os.access(sysctl_args['key_path'], os.F_OK): - return 1, 'key_path is not an existing file, key %s seems invalid' % sysctl_args['key_path'] - if not os.access(sysctl_args['key_path'], os.R_OK): - return 1, 'key_path is not a readable file, key seems to be uncheckable' - - # checks before - if current_step == 'before' and sysctl_args['checks'] in ['before', 'both']: - if sysctl_args['key_path'] is not None and not os.access(sysctl_args['key_path'], os.W_OK): - return 1, 'key_path is not a writable file, key seems to be read only' - return 0, '' - - # checks after - if current_step == 'after' and sysctl_args['checks'] in ['after', 'both']: - if sysctl_args['value'] is not None: - if sysctl_args['key_path'] is not None: - # reading the virtual file - f = open(sysctl_args['key_path'],'r') - output = f.read() - f.close() - else: - # we're on a system without /proc (ie. freebsd), so just - # use the sysctl command to get the currently set value - sysctl_cmd = module.get_bin_path('sysctl', required=True) - rc,output,stderr = module.run_command("%s -n %s" % (sysctl_cmd, sysctl_args['name'])) - if rc != 0: - return 1, 'failed to lookup the value via the sysctl command' - - output = output.strip(' \t\n\r') - output = re.sub(r'\s+', ' ', output) - - # normal case, found value must be equal to the submitted value, and - # we compare the exploded values to handle any whitepsace differences - if output.split() != sysctl_args['value'].split(): - return 1, 'key seems not set to value even after update/sysctl, founded : <%s>, wanted : <%s>' % (output, sysctl_args['value']) - - return 0, '' + # Whitespace is bad + self.args['name'] = self.args['name'].strip() + if self.args['value'] is not None: + self.args['value'] = self.args['value'].strip() else: - # no value was supplied, so we're checking to make sure - # the associated name is absent. We just fudge this since - # the sysctl isn't really gone, just removed from the conf - # file meaning it will be whatever the system default is - return 0, '' + self.args['value'] = "" + + thisname = self.args['name'] + + # get the current proc fs value + self.proc_value = self.get_token_curr_value(thisname) + + # get the currect sysctl file value + self.read_sysctl_file() + if thisname not in self.file_values: + self.file_values[thisname] = None + + # update file contents with desired token/value + self.fix_lines() + + # what do we need to do now? + if self.file_values[thisname] is None and self.args['state'] == "present": + self.changed = True + self.write_file = True + elif self.file_values[thisname] != self.args['value']: + self.changed = True + self.write_file = True + if self.args['sysctl_set']: + if self.proc_value is None: + self.changed = True + elif self.proc_value != self.args['value']: + self.changed = True + self.set_proc = True + + # Do the work + if not self.module.check_mode: + if self.write_file: + self.write_sysctl() + if self.write_file and self.args['reload']: + self.reload_sysctl() + if self.set_proc: + self.set_token_value(self.args['name'], self.args['value']) + + # ============================================================== + # SYSCTL COMMAND MANAGEMENT + # ============================================================== + + # Use the sysctl command to find the current value + def get_token_curr_value(self, token): + thiscmd = "%s -e -n %s" % (self.sysctl_cmd, token) + rc,out,err = self.module.run_command(thiscmd) + if rc != 0: + return None + else: + return out + + # Use the sysctl command to set the current value + def set_token_value(self, token, value): + if len(value.split()) > 0: + value = '"' + value + '"' + thiscmd = "%s -w %s=%s" % (self.sysctl_cmd, token, value) + rc,out,err = self.module.run_command(thiscmd) + if rc != 0: + self.module.fail_json(msg='setting %s failed: %s' % (token, out + err)) + else: + return rc + + # Run sysctl -p + def reload_sysctl(self): + # do it + if get_platform().lower() == 'freebsd': + # freebsd doesn't support -p, so reload the sysctl service + rc,out,err = self.module.run_command('/etc/rc.d/sysctl reload') + else: + # system supports reloading via the -p flag to sysctl, so we'll use that + rc,out,err = self.module.run_command([self.sysctl_cmd, '-p', self.sysctl_file]) + + if rc != 0: + self.module.fail_json(msg="Failed to reload sysctl: %s" % str(out) + str(err)) + + # ============================================================== + # SYSCTL FILE MANAGEMENT + # ============================================================== + + # Get the token value from the sysctl file + def read_sysctl_file(self): + lines = open(self.sysctl_file, "r").readlines() + for line in lines: + line = line.strip() + self.file_lines.append(line) + + # don't split empty lines or comments + if not line or line.startswith("#"): + continue + + k, v = line.split('=',1) + k = k.strip() + v = v.strip() + self.file_values[k] = v.strip() + + # Fix the value in the sysctl file content + def fix_lines(self): + checked = [] + self.fixed_lines = [] + for line in self.file_lines: + if not line.strip() or line.strip().startswith("#"): + self.fixed_lines.append(line) + continue + tmpline = line.strip() + k, v = line.split('=',1) + k = k.strip() + v = v.strip() + if k not in checked: + checked.append(k) + if k == self.args['name']: + if self.args['state'] == "present": + new_line = "%s = %s\n" % (k, self.args['value']) + self.fixed_lines.append(new_line) + else: + new_line = "%s = %s\n" % (k, v) + self.fixed_lines.append(new_line) + + if self.args['name'] not in checked and self.args['state'] == "present": + new_line = "%s = %s\n" % (self.args['name'], self.args['value']) + self.fixed_lines.append(new_line) + + # Completely rewrite the sysctl file + def write_sysctl(self): + # open a tmp file + fd, tmp_path = tempfile.mkstemp('.conf', '.ansible_m_sysctl_', os.path.dirname(self.sysctl_file)) + f = open(tmp_path,"w") + try: + for l in self.fixed_lines: + f.write(l.strip() + "\n") + except IOError, e: + self.module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, str(e))) + f.flush() + f.close() + + # replace the real one + self.module.atomic_move(tmp_path, self.sysctl_file) - # weird end - return 1, 'unexpected position reached' # ============================================================== # main @@ -233,110 +272,16 @@ def main(): name = dict(aliases=['key'], required=True), value = dict(aliases=['val'], required=False), state = dict(default='present', choices=['present', 'absent']), - checks = dict(default='both', choices=['none', 'before', 'after', 'both']), reload = dict(default=True, type='bool'), + sysctl_set = dict(default=False, type='bool'), sysctl_file = dict(default='/etc/sysctl.conf') - ) + ), + supports_check_mode=True ) - # defaults - sysctl_args = { - 'changed': False, - 'name': module.params['name'], - 'state': module.params['state'], - 'checks': module.params['checks'], - 'reload': module.params['reload'], - 'value': module.params.get('value'), - 'sysctl_file': module.params['sysctl_file'] - } - - # prepare vars - sysctl_args = sysctl_args_expand(**sysctl_args) - if get_platform().lower() == 'freebsd': - # freebsd does not like spaces around the equal sign - pattern = "%s=%s\n" - else: - pattern = "%s = %s\n" - new_line = pattern % (sysctl_args['name'], sysctl_args['value']) - to_write = [] - founded = False - - # make checks before act - res,msg = sysctl_check(module, 'before', **sysctl_args) - if res != 0: - module.fail_json(msg='checks_before failed with: ' + msg) + result = SysctlModule(module) - if not os.access(sysctl_args['sysctl_file'], os.W_OK): - try: - f = open(sysctl_args['sysctl_file'],'w') - f.close() - except IOError, e: - module.fail_json(msg='unable to create supplied sysctl file (destination directory probably missing)') - - # reading the file - for line in open(sysctl_args['sysctl_file'], 'r').readlines(): - if not line.strip(): - to_write.append(line) - continue - if line.strip().startswith('#'): - to_write.append(line) - continue - - # write line if not the one searched - ld = {} - ld['name'], ld['val'] = line.split('=',1) - ld['name'] = ld['name'].strip() - - if ld['name'] != sysctl_args['name']: - to_write.append(line) - continue - - # should be absent ? - if sysctl_args['state'] == 'absent': - # not writing the founded line - # mark as changed - sysctl_args['changed'] = True - - # should be present - if sysctl_args['state'] == 'present': - # is the founded line equal to the wanted one ? - ld['val'] = ld['val'].strip() - if ld['val'] == sysctl_args['value']: - # line is equal, writing it without update (but cancel repeats) - if sysctl_args['changed'] == False and founded == False: - to_write.append(line) - founded = True - else: - # update the line (but cancel repeats) - if sysctl_args['changed'] == False and founded == False: - to_write.append(new_line) - sysctl_args['changed'] = True - continue - - # if not changed, but should be present, so we have to add it - if sysctl_args['state'] == 'present' and sysctl_args['changed'] == False and founded == False: - to_write.append(new_line) - sysctl_args['changed'] = True - - # has changed ? - res = 0 - if sysctl_args['changed'] == True: - sysctl_args = write_sysctl(module, to_write, **sysctl_args) - res,msg = reload_sysctl(module, **sysctl_args) - - # make checks after act - res,msg = sysctl_check(module, 'after', **sysctl_args) - if res != 0: - module.fail_json(msg='checks_after failed with: ' + msg) - - # look at the next link to avoid this workaround - # https://groups.google.com/forum/?fromgroups=#!topic/ansible-project/LMY-dwF6SQk - changed = sysctl_args['changed'] - del sysctl_args['changed'] - - # end - sysctl_args = sysctl_args_collapse(**sysctl_args) - module.exit_json(changed=changed, **sysctl_args) + module.exit_json(changed=result.changed) sys.exit(0) # import module snippets diff --git a/library/system/user b/library/system/user index 48bcf75171..0e29e443bf 100644 --- a/library/system/user +++ b/library/system/user @@ -77,8 +77,8 @@ options: description: - Optionally set the user's password to this crypted value. See the user example in the github examples directory for what this looks - like in a playbook. - - Passwords values can be generated with "openssl passwd -salt <salt> -1 <plaintext>" + like in a playbook. The `FAQ <http://docs.ansible.com/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module>`_ + contains details on various ways to generate these password values. state: required: false default: "present" @@ -901,8 +901,21 @@ class OpenBSDUser(User): cmd.append(self.shell) if self.login_class is not None: - cmd.append('-L') - cmd.append(self.login_class) + # find current login class + user_login_class = None + userinfo_cmd = [self.module.get_bin_path('userinfo', True), self.name] + (rc, out, err) = self.execute_command(userinfo_cmd) + + for line in out.splitlines(): + tokens = line.split() + + if tokens[0] == 'class' and len(tokens) == 2: + user_login_class = tokens[1] + + # act only if login_class change + if self.login_class != user_login_class: + cmd.append('-L') + cmd.append(self.login_class) if self.update_password == 'always' and self.password is not None and info[1] != self.password: cmd.append('-p') diff --git a/library/utilities/fail b/library/utilities/fail index 7023f357ca..23f5b83668 100644 --- a/library/utilities/fail +++ b/library/utilities/fail @@ -40,5 +40,5 @@ author: Dag Wieers EXAMPLES = ''' # Example playbook using fail and when together - fail: msg="The system may not be provisioned according to the CMDB status." - when: "{{ cmdb_status }} != 'to-be-staged'" + when: cmdb_status != "to-be-staged" ''' diff --git a/library/web_infrastructure/django_manage b/library/web_infrastructure/django_manage index f6ea9c4914..68eb92c1bf 100644 --- a/library/web_infrastructure/django_manage +++ b/library/web_infrastructure/django_manage @@ -129,12 +129,11 @@ def _ensure_virtualenv(module): if venv_param is None: return - virtualenv = module.get_bin_path('virtualenv', True) - vbin = os.path.join(os.path.expanduser(venv_param), 'bin') activate = os.path.join(vbin, 'activate') if not os.path.exists(activate): + virtualenv = module.get_bin_path('virtualenv', True) vcmd = '%s %s' % (virtualenv, venv_param) vcmd = [virtualenv, venv_param] rc, out_venv, err_venv = module.run_command(vcmd) diff --git a/packaging/rpm/ansible.spec b/packaging/rpm/ansible.spec index 6088bd7ba6..ce929e4cdf 100644 --- a/packaging/rpm/ansible.spec +++ b/packaging/rpm/ansible.spec @@ -79,9 +79,8 @@ are transferred to managed machines automatically. mkdir -p %{buildroot}/etc/ansible/ cp examples/hosts %{buildroot}/etc/ansible/ cp examples/ansible.cfg %{buildroot}/etc/ansible/ -mkdir -p %{buildroot}/%{_mandir}/{man1,man3}/ +mkdir -p %{buildroot}/%{_mandir}/man1/ cp -v docs/man/man1/*.1 %{buildroot}/%{_mandir}/man1/ -cp -v docs/man/man3/*.3 %{buildroot}/%{_mandir}/man3/ mkdir -p %{buildroot}/%{_datadir}/ansible cp -rv library/* %{buildroot}/%{_datadir}/ansible/ @@ -98,7 +97,6 @@ rm -rf %{buildroot} %config(noreplace) %{_sysconfdir}/ansible %doc README.md PKG-INFO COPYING %doc %{_mandir}/man1/ansible* -%doc %{_mandir}/man3/ansible.* %doc examples/playbooks diff --git a/plugins/inventory/ec2.ini b/plugins/inventory/ec2.ini index 01a4982d62..9d05dfad03 100644 --- a/plugins/inventory/ec2.ini +++ b/plugins/inventory/ec2.ini @@ -12,7 +12,7 @@ # in AWS and merge the results together. Alternatively, set this to a comma # separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2' regions = all -regions_exclude = us-gov-west-1 +regions_exclude = us-gov-west-1,cn-north-1 # When generating inventory, Ansible needs to know how to address a server. # Each EC2 instance has a lot of variables associated with it. Here is the list: @@ -47,7 +47,7 @@ route53 = False # will be written to this directory: # - ansible-ec2.cache # - ansible-ec2.index -cache_path = /tmp +cache_path = ~/.ansible/tmp # The number of seconds a cache file is considered valid. After this many # seconds, a new API call will be made, and the cache file will be updated. diff --git a/plugins/inventory/ec2.py b/plugins/inventory/ec2.py index a38545052d..c652da26bf 100755 --- a/plugins/inventory/ec2.py +++ b/plugins/inventory/ec2.py @@ -223,9 +223,12 @@ class Ec2Inventory(object): config.get('ec2', 'route53_excluded_zones', '').split(',')) # Cache related - cache_path = config.get('ec2', 'cache_path') - self.cache_path_cache = cache_path + "/ansible-ec2.cache" - self.cache_path_index = cache_path + "/ansible-ec2.index" + cache_dir = os.path.expanduser(config.get('ec2', 'cache_path')) + if not os.path.exists(cache_dir): + os.makedirs(cache_dir) + + self.cache_path_cache = cache_dir + "/ansible-ec2.cache" + self.cache_path_index = cache_dir + "/ansible-ec2.index" self.cache_max_age = config.getint('ec2', 'cache_max_age') @@ -295,9 +298,10 @@ class Ec2Inventory(object): for instance in instances: self.add_rds_instance(instance, region) except boto.exception.BotoServerError as e: - print "Looks like AWS RDS is down: " - print e - sys.exit(1) + if not e.reason == "Forbidden": + print "Looks like AWS RDS is down: " + print e + sys.exit(1) def get_instance(self, region, instance_id): ''' Gets details about a specific instance ''' diff --git a/test/TestInventory.py b/test/TestInventory.py index e48e70ae94..60ebeb7fc2 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -163,6 +163,21 @@ class TestInventory(unittest.TestCase): var = inventory.get_variables('FE80:EF45::12:1') self.assertEqual(var['ansible_ssh_port'], 2222) + def test_simple_string_fqdn(self): + inventory = Inventory('foo.example.com,bar.example.com') + hosts = inventory.list_hosts() + self.assertEqual(sorted(hosts), sorted(['foo.example.com','bar.example.com'])) + + def test_simple_string_fqdn_port(self): + inventory = Inventory('foo.example.com:2222,bar.example.com') + hosts = inventory.list_hosts() + self.assertEqual(sorted(hosts), sorted(['foo.example.com','bar.example.com'])) + + def test_simple_string_fqdn_vars(self): + inventory = Inventory('foo.example.com:2222,bar.example.com') + var = inventory.get_variables('foo.example.com') + self.assertEqual(var['ansible_ssh_port'], 2222) + def test_simple_vars(self): inventory = self.simple_inventory() vars = inventory.get_variables('thor') @@ -254,6 +269,7 @@ class TestInventory(unittest.TestCase): expected2 = ['rtp_a', 'rtp_b'] expected3 = ['rtp_a', 'rtp_b', 'rtp_c', 'tri_a', 'tri_b', 'tri_c'] expected4 = ['rtp_b', 'orlando' ] + expected5 = ['blade-a-1'] inventory = self.complex_inventory() hosts = inventory.list_hosts("nc[1]") @@ -264,6 +280,8 @@ class TestInventory(unittest.TestCase): self.compare(hosts, expected3, sort=False) hosts = inventory.list_hosts("nc[1-2]:florida[0-1]") self.compare(hosts, expected4, sort=False) + hosts = inventory.list_hosts("blade-a-1") + self.compare(hosts, expected5, sort=False) def test_complex_intersect(self): inventory = self.complex_inventory() diff --git a/test/TestUtils.py b/test/TestUtils.py index fbdbe0f405..0e7a64a481 100644 --- a/test/TestUtils.py +++ b/test/TestUtils.py @@ -112,27 +112,27 @@ class TestUtils(unittest.TestCase): # boolean assert(ansible.utils.check_conditional( - 'jinja2_compare true', '/', {}) == True) + 'true', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare false', '/', {}) == False) + 'false', '/', {}) == False) assert(ansible.utils.check_conditional( - 'jinja2_compare True', '/', {}) == True) + 'True', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare False', '/', {}) == False) + 'False', '/', {}) == False) # integer assert(ansible.utils.check_conditional( - 'jinja2_compare 1', '/', {}) == True) + '1', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare 0', '/', {}) == False) + '0', '/', {}) == False) # string, beware, a string is truthy unless empty assert(ansible.utils.check_conditional( - 'jinja2_compare "yes"', '/', {}) == True) + '"yes"', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare "no"', '/', {}) == True) + '"no"', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare ""', '/', {}) == False) + '""', '/', {}) == False) def test_check_conditional_jinja2_variable_literals(self): @@ -140,61 +140,61 @@ class TestUtils(unittest.TestCase): # boolean assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'True'}) == True) + 'var', '/', {'var': 'True'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'true'}) == True) + 'var', '/', {'var': 'true'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'False'}) == False) + 'var', '/', {'var': 'False'}) == False) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'false'}) == False) + 'var', '/', {'var': 'false'}) == False) # integer assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '1'}) == True) + 'var', '/', {'var': '1'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 1}) == True) + 'var', '/', {'var': 1}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '0'}) == False) + 'var', '/', {'var': '0'}) == False) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 0}) == False) + 'var', '/', {'var': 0}) == False) # string, beware, a string is truthy unless empty assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '"yes"'}) == True) + 'var', '/', {'var': '"yes"'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '"no"'}) == True) + 'var', '/', {'var': '"no"'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '""'}) == False) + 'var', '/', {'var': '""'}) == False) # Python boolean in Jinja2 expression assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': True}) == True) + 'var', '/', {'var': True}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': False}) == False) + 'var', '/', {'var': False}) == False) def test_check_conditional_jinja2_expression(self): assert(ansible.utils.check_conditional( - 'jinja2_compare 1 == 1', '/', {}) == True) + '1 == 1', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare bar == 42', '/', {'bar': 42}) == True) + 'bar == 42', '/', {'bar': 42}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare bar != 42', '/', {'bar': 42}) == False) + 'bar != 42', '/', {'bar': 42}) == False) def test_check_conditional_jinja2_expression_in_variable(self): assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '1 == 1'}) == True) + 'var', '/', {'var': '1 == 1'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'bar == 42', 'bar': 42}) == True) + 'var', '/', {'var': 'bar == 42', 'bar': 42}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'bar != 42', 'bar': 42}) == False) + 'var', '/', {'var': 'bar != 42', 'bar': 42}) == False) def test_check_conditional_jinja2_unicode(self): assert(ansible.utils.check_conditional( - u'jinja2_compare "\u00df"', '/', {}) == True) + u'"\u00df"', '/', {}) == True) assert(ansible.utils.check_conditional( - u'jinja2_compare var == "\u00df"', '/', {'var': u'\u00df'}) == True) + u'var == "\u00df"', '/', {'var': u'\u00df'}) == True) ##################################### diff --git a/test/complex_hosts b/test/complex_hosts index 0b5ce8c19c..d7f172f203 100644 --- a/test/complex_hosts +++ b/test/complex_hosts @@ -87,3 +87,9 @@ host[2:3] [role3] host[1:3:2] + +[role4] +blade-[a:c]-[1:16] +blade-[d:z]-[01:16].example.com +blade-[1:10]-[1:16] +host-e-[10:16].example.net:1234