diff options
author | Ronan Amicel <ronan.amicel@gmail.com> | 2012-09-14 00:36:10 +0200 |
---|---|---|
committer | Ronan Amicel <ronan.amicel@gmail.com> | 2012-09-21 02:25:51 +0200 |
commit | cdda68e8e24f6390e5d443ab3c44f7b6bc1a35da (patch) | |
tree | 49a5c24d90695e954c21e0e76f6abd87049e1064 | |
parent | Remove unused parameter (diff) | |
download | fabtools-cdda68e8e24f6390e5d443ab3c44f7b6bc1a35da.tar.xz |
Improve documentation
70 files changed, 2026 insertions, 232 deletions
@@ -6,6 +6,7 @@ bin/ build/ dist/ +docs/_build/ include/ lib/ man/ @@ -1,29 +1,29 @@ -Introduction -============ +About +===== ``fabtools`` includes useful functions to help you write your `Fabric <http://fabfile.org/>`_ files. ``fabtools`` makes it easier to manage system users, packages, databases, etc. -``fabtools`` includes a number of low-level actions, as well as a higher level interface called ``require``. +``fabtools`` includes a number of low-level actions, as well as a higher level interface called ``fabtools.require``. -Using ``require`` allows you to use a more declarative style, similar to Chef or Puppet. +Using ``fabtools.require`` allows you to use a more declarative style, similar to Chef or Puppet. Installing ========== -To install the latest release from PyPI:: +To install the latest release from `PyPI <http://pypi.python.org/pypi/fabtools>`_:: $ pip install fabtools -To install the latest development version from GitHub:: +To install the latest development version from `GitHub <https://github.com/ronnix/fabtools>`_:: $ pip install git+git://github.com/ronnix/fabtools.git Example ======= -Here is an example fabfile using ``fabtools``:: +Here is an example ``fabfile.py`` using ``fabtools``:: from fabric.api import * from fabtools import require @@ -76,45 +76,3 @@ Supported targets * Ubuntu 11.04 * Ubuntu 11.10 * Ubuntu 12.04 LTS - -Tests -===== - -Running tests -------------- - -If you're using Python 2.7, you can launch the tests using the built-in `unittest <http://docs.python.org/library/unittest.html>`_ runner:: - - $ python -m unittest discover - -If you're using Python 2.5 or 2.6, you'll need to install `unittest2 <http://pypi.python.org/pypi/unittest2>`_, and use the provided runner:: - - $ pip install unittest2 - $ unit2 discover - -Or you can run the tests on all supported Python versions using `tox <http://pypi.python.org/pypi/tox>`_, which will take care of everything:: - - $ pip install tox - $ tox - -Unit tests ----------- - -Running unit tests requires the `mock <http://pypi.python.org/pypi/mock/>`_ library. - -Functional tests ----------------- - -Running functional tests requires `Vagrant <http://vagrantup.com/>`_ to launch virtual machines, -against which all the tests will be run. - -If Vagrant is not installed, the functional tests will be skipped automatically. - -If Vagrant is installed, the default is to run the tests on all available base boxes. -You can specify which base boxes should be used by setting the VAGRANT_BOXES environment variable:: - - $ VAGRANT_BOXES='ubuntu_10_04 ubuntu_12_04' tox -e py27 - -You can also use this to manually disable functional tests:: - - $ VAGRANT_BOXES='' tox diff --git a/CHANGELOG.rst b/docs/CHANGELOG.rst index bf5f728..bf5f728 100644 --- a/CHANGELOG.rst +++ b/docs/CHANGELOG.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..0d9e14c --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/fabtools.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/fabtools.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/fabtools" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/fabtools" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/api/cron.rst b/docs/api/cron.rst new file mode 100644 index 0000000..13f7a25 --- /dev/null +++ b/docs/api/cron.rst @@ -0,0 +1,7 @@ +.. _cron_module: + +:mod:`fabtools.cron` +-------------------- + +.. automodule:: fabtools.cron + :members: diff --git a/docs/api/deb.rst b/docs/api/deb.rst new file mode 100644 index 0000000..ad47133 --- /dev/null +++ b/docs/api/deb.rst @@ -0,0 +1,9 @@ +.. _deb_module: + +:mod:`fabtools.deb` +------------------- + +.. automodule:: fabtools.deb + :members: + + .. seealso:: :ref:`require_deb_module` diff --git a/docs/api/files.rst b/docs/api/files.rst new file mode 100644 index 0000000..4c82a47 --- /dev/null +++ b/docs/api/files.rst @@ -0,0 +1,9 @@ +.. _files_module: + +:mod:`fabtools.files` +--------------------- + +.. automodule:: fabtools.files + :members: + + .. seealso:: :ref:`require_files_module` diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 0000000..3084c04 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,21 @@ +fabtools +======== + +.. toctree:: + :maxdepth: 1 + + cron + deb + files + mysql + network + openvz + postgres + python + python_distribute + service + shorewall + supervisor + system + user + vagrant diff --git a/docs/api/mysql.rst b/docs/api/mysql.rst new file mode 100644 index 0000000..fd1ab4f --- /dev/null +++ b/docs/api/mysql.rst @@ -0,0 +1,20 @@ +.. _mysql_module: + +:mod:`fabtools.mysql` +--------------------- + +.. automodule:: fabtools.mysql + + .. seealso:: :ref:`require_mysql_module` + + Manage users + ~~~~~~~~~~~~ + + .. autofunction:: user_exists + .. autofunction:: create_user + + Manage databases + ~~~~~~~~~~~~~~~~ + + .. autofunction:: database_exists + .. autofunction:: create_database diff --git a/docs/api/network.rst b/docs/api/network.rst new file mode 100644 index 0000000..4132935 --- /dev/null +++ b/docs/api/network.rst @@ -0,0 +1,7 @@ +.. _network_module: + +:mod:`fabtools.network` +----------------------- + +.. automodule:: fabtools.network + :members: diff --git a/docs/api/openvz.rst b/docs/api/openvz.rst new file mode 100644 index 0000000..9420494 --- /dev/null +++ b/docs/api/openvz.rst @@ -0,0 +1,38 @@ +.. _openvz_module: + +:mod:`fabtools.openvz` +---------------------- + +.. automodule:: fabtools.openvz + + .. seealso:: :ref:`require_openvz_module` + + Manage templates + ~~~~~~~~~~~~~~~~ + + .. autofunction:: download_template + + Manage containers + ~~~~~~~~~~~~~~~~~ + + .. autofunction:: exists + .. autofunction:: create + .. autofunction:: set + .. autofunction:: status + .. autofunction:: start + .. autofunction:: stop + .. autofunction:: restart + .. autofunction:: destroy + + + Run commands inside a container + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + .. autofunction:: exec2 + .. autofunction:: guest + + Container class + ~~~~~~~~~~~~~~~ + + .. autoclass:: fabtools.openvz.container.Container + :members: diff --git a/docs/api/postgres.rst b/docs/api/postgres.rst new file mode 100644 index 0000000..cc4ff32 --- /dev/null +++ b/docs/api/postgres.rst @@ -0,0 +1,20 @@ +.. _postgres_module: + +:mod:`fabtools.postgres` +------------------------ + +.. automodule:: fabtools.postgres + + .. seealso:: :ref:`require_postgres_module` + + Manage users + ~~~~~~~~~~~~ + + .. autofunction:: user_exists + .. autofunction:: create_user + + Manage databases + ~~~~~~~~~~~~~~~~ + + .. autofunction:: database_exists + .. autofunction:: create_database diff --git a/docs/api/python.rst b/docs/api/python.rst new file mode 100644 index 0000000..0938741 --- /dev/null +++ b/docs/api/python.rst @@ -0,0 +1,26 @@ +.. _python_module: + +:mod:`fabtools.python` +---------------------- + +.. automodule:: fabtools.python + + .. seealso:: :ref:`python_distribute_module` and :ref:`require_python_module` + + Virtual environments + ~~~~~~~~~~~~~~~~~~~~ + + .. autofunction:: virtualenv + + Installing pip + ~~~~~~~~~~~~~~ + + .. autofunction:: is_pip_installed + .. autofunction:: install_pip + + Installing packages + ~~~~~~~~~~~~~~~~~~~ + + .. autofunction:: is_installed + .. autofunction:: install + .. autofunction:: install_requirements diff --git a/docs/api/python_distribute.rst b/docs/api/python_distribute.rst new file mode 100644 index 0000000..8f00478 --- /dev/null +++ b/docs/api/python_distribute.rst @@ -0,0 +1,9 @@ +.. _python_distribute_module: + +:mod:`fabtools.python_distribute` +--------------------------------- + +.. automodule:: fabtools.python_distribute + :members: + + .. seealso:: :ref:`python_module` and :ref:`require_python_module` diff --git a/docs/api/require/deb.rst b/docs/api/require/deb.rst new file mode 100644 index 0000000..6d5a9c4 --- /dev/null +++ b/docs/api/require/deb.rst @@ -0,0 +1,22 @@ +.. _require_deb_module: + +:mod:`fabtools.require.deb` +--------------------------- + +.. automodule:: fabtools.require.deb + + .. seealso:: :ref:`deb_module` + + Repositories + ~~~~~~~~~~~~ + + .. autofunction:: source + .. autofunction:: ppa + + Packages + ~~~~~~~~ + + .. autofunction:: package + .. autofunction:: packages + .. autofunction:: nopackage + .. autofunction:: nopackages diff --git a/docs/api/require/files.rst b/docs/api/require/files.rst new file mode 100644 index 0000000..07dd030 --- /dev/null +++ b/docs/api/require/files.rst @@ -0,0 +1,9 @@ +.. _require_files_module: + +:mod:`fabtools.require.files` +--------------------------- + +.. automodule:: fabtools.require.files + :members: + + .. seealso:: :ref:`files_module` diff --git a/docs/api/require/index.rst b/docs/api/require/index.rst new file mode 100644 index 0000000..cd516f0 --- /dev/null +++ b/docs/api/require/index.rst @@ -0,0 +1,20 @@ +fabtools.require +================ + +.. toctree:: + :maxdepth: 1 + + deb + files + mysql + nginx + openvz + postfix + postgres + python + redis + service + shorewall + supervisor + system + users diff --git a/docs/api/require/mysql.rst b/docs/api/require/mysql.rst new file mode 100644 index 0000000..b8278e9 --- /dev/null +++ b/docs/api/require/mysql.rst @@ -0,0 +1,9 @@ +.. _require_mysql_module: + +:mod:`fabtools.require.mysql` +----------------------------- + +.. automodule:: fabtools.require.mysql + :members: + + .. seealso:: :ref:`mysql_module` diff --git a/docs/api/require/nginx.rst b/docs/api/require/nginx.rst new file mode 100644 index 0000000..0739ae2 --- /dev/null +++ b/docs/api/require/nginx.rst @@ -0,0 +1,7 @@ +.. _require_nginx_module: + +:mod:`fabtools.require.nginx` +----------------------------- + +.. automodule:: fabtools.require.nginx + :members: diff --git a/docs/api/require/openvz.rst b/docs/api/require/openvz.rst new file mode 100644 index 0000000..3773eb5 --- /dev/null +++ b/docs/api/require/openvz.rst @@ -0,0 +1,9 @@ +.. _require_openvz_module: + +:mod:`fabtools.require.openvz` +------------------------------ + +.. automodule:: fabtools.require.openvz + :members: + + .. seealso:: :ref:`openvz_module` diff --git a/docs/api/require/postfix.rst b/docs/api/require/postfix.rst new file mode 100644 index 0000000..0b01221 --- /dev/null +++ b/docs/api/require/postfix.rst @@ -0,0 +1,7 @@ +.. _require_postfix_module: + +:mod:`fabtools.require.postfix` +------------------------------- + +.. automodule:: fabtools.require.postfix + :members: diff --git a/docs/api/require/postgres.rst b/docs/api/require/postgres.rst new file mode 100644 index 0000000..410342d --- /dev/null +++ b/docs/api/require/postgres.rst @@ -0,0 +1,9 @@ +.. _require_postgres_module: + +:mod:`fabtools.require.postgres` +-------------------------------- + +.. automodule:: fabtools.require.postgres + :members: + + .. seealso:: :ref:`postgres_module` diff --git a/docs/api/require/python.rst b/docs/api/require/python.rst new file mode 100644 index 0000000..1175eec --- /dev/null +++ b/docs/api/require/python.rst @@ -0,0 +1,22 @@ +.. _require_python_module: + +:mod:`fabtools.require.python` +------------------------------ + +.. automodule:: fabtools.require.python + + .. seealso:: :ref:`python_module` + + Virtual environments + ~~~~~~~~~~~~~~~~~~~~ + + .. autofunction:: virtualenv + + Installing packages + ~~~~~~~~~~~~~~~~~~~ + + .. autofunction:: package + .. autofunction:: packages + .. autofunction:: requirements + .. autofunction:: pip + .. autofunction:: distribute diff --git a/docs/api/require/redis.rst b/docs/api/require/redis.rst new file mode 100644 index 0000000..6e97c97 --- /dev/null +++ b/docs/api/require/redis.rst @@ -0,0 +1,7 @@ +.. _require_redis_module: + +:mod:`fabtools.require.redis` +----------------------------- + +.. automodule:: fabtools.require.redis + :members: diff --git a/docs/api/require/service.rst b/docs/api/require/service.rst new file mode 100644 index 0000000..f223134 --- /dev/null +++ b/docs/api/require/service.rst @@ -0,0 +1,9 @@ +.. _require_service_module: + +:mod:`fabtools.require.service` +------------------------------- + +.. automodule:: fabtools.require.service + :members: + + .. seealso:: :ref:`service_module` diff --git a/docs/api/require/shorewall.rst b/docs/api/require/shorewall.rst new file mode 100644 index 0000000..a5888e1 --- /dev/null +++ b/docs/api/require/shorewall.rst @@ -0,0 +1,9 @@ +.. _require_shorewall_module: + +:mod:`fabtools.require.shorewall` +--------------------------------- + +.. automodule:: fabtools.require.shorewall + :members: + + .. seealso:: :ref:`shorewall_module` diff --git a/docs/api/require/supervisor.rst b/docs/api/require/supervisor.rst new file mode 100644 index 0000000..e8cfa6f --- /dev/null +++ b/docs/api/require/supervisor.rst @@ -0,0 +1,9 @@ +.. _require_supervisor_module: + +:mod:`fabtools.require.supervisor` +---------------------------------- + +.. automodule:: fabtools.require.supervisor + :members: + + .. seealso:: :ref:`supervisor_module` diff --git a/docs/api/require/system.rst b/docs/api/require/system.rst new file mode 100644 index 0000000..d0d80f4 --- /dev/null +++ b/docs/api/require/system.rst @@ -0,0 +1,18 @@ +.. _require_system_module: + +:mod:`fabtools.require.system` +------------------------------ + +.. automodule:: fabtools.require.system + + .. seealso:: :ref:`system_module` + + .. autofunction:: hostname + .. autofunction:: sysctl + + Locales + ~~~~~~~ + + .. autofunction:: default_locale + .. autofunction:: locale + .. autofunction:: locales diff --git a/docs/api/require/users.rst b/docs/api/require/users.rst new file mode 100644 index 0000000..7513113 --- /dev/null +++ b/docs/api/require/users.rst @@ -0,0 +1,9 @@ +.. _require_users_module: + +:mod:`fabtools.require.users` +----------------------------- + +.. automodule:: fabtools.require.users + :members: + + .. seealso:: :ref:`user_module` diff --git a/docs/api/service.rst b/docs/api/service.rst new file mode 100644 index 0000000..4dcb229 --- /dev/null +++ b/docs/api/service.rst @@ -0,0 +1,9 @@ +.. _service_module: + +:mod:`fabtools.service` +----------------------- + +.. automodule:: fabtools.service + :members: + + .. seealso:: :ref:`require_service_module` diff --git a/docs/api/shorewall.rst b/docs/api/shorewall.rst new file mode 100644 index 0000000..67a914c --- /dev/null +++ b/docs/api/shorewall.rst @@ -0,0 +1,26 @@ +.. _shorewall_module: + +:mod:`fabtools.shorewall` +------------------------ + +.. automodule:: fabtools.shorewall + + .. seealso:: :ref:`require_shorewall_module` + + Firewall status + ~~~~~~~~~~~~~~~ + + .. autofunction:: status + .. autofunction:: is_started + .. autofunction:: is_stopped + + Firewall rules + ~~~~~~~~~~~~~~ + + .. autofunction:: rule + .. autofunction:: hosts + .. autofunction:: Ping + .. autofunction:: SSH + .. autofunction:: HTTP + .. autofunction:: HTTPS + .. autofunction:: SMTP diff --git a/docs/api/supervisor.rst b/docs/api/supervisor.rst new file mode 100644 index 0000000..4e585ce --- /dev/null +++ b/docs/api/supervisor.rst @@ -0,0 +1,22 @@ +.. _supervisor_module: + +:mod:`fabtools.supervisor` +-------------------------- + +.. automodule:: fabtools.supervisor + + .. seealso:: :ref:`require_supervisor_module` + + Manage supervisord + ~~~~~~~~~~~~~~~~~~ + + .. autofunction:: reload_config + .. autofunction:: update_config + + Manage processes + ~~~~~~~~~~~~~~~~ + + .. autofunction:: process_status + .. autofunction:: start_process + .. autofunction:: stop_process + .. autofunction:: restart_process diff --git a/docs/api/system.rst b/docs/api/system.rst new file mode 100644 index 0000000..9c7d11e --- /dev/null +++ b/docs/api/system.rst @@ -0,0 +1,23 @@ +.. _system_module: + +:mod:`fabtools.system` +---------------------- + +.. automodule:: fabtools.system + + Hostname + ~~~~~~~~ + + .. autofunction:: get_hostname + .. autofunction:: set_hostname + + Kernel parameters + ~~~~~~~~~~~~~~~~~ + + .. autofunction:: get_sysctl + .. autofunction:: set_sysctl + + Locales + ~~~~~~~ + + .. autofunction:: supported_locales diff --git a/docs/api/user.rst b/docs/api/user.rst new file mode 100644 index 0000000..c072861 --- /dev/null +++ b/docs/api/user.rst @@ -0,0 +1,9 @@ +.. _user_module: + +:mod:`fabtools.user` +-------------------- + +.. automodule:: fabtools.user + :members: + + .. seealso:: :ref:`require_users_module` diff --git a/docs/api/vagrant.rst b/docs/api/vagrant.rst new file mode 100644 index 0000000..83a213c --- /dev/null +++ b/docs/api/vagrant.rst @@ -0,0 +1,9 @@ +.. _vagrant_module: + +:mod:`fabtools.vagrant` +----------------------- + +.. automodule:: fabtools.vagrant + :members: + + .. autofunction:: vagrant diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..412b671 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +# +# fabtools documentation build configuration file, created by +# sphinx-quickstart on Thu Sep 13 17:22:32 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', +] + +# Preserve source order in API documentation +autodoc_member_order = 'bysource' + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'fabtools' +copyright = u'2012, Ronan Amicel' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.5' +# The full version, including alpha/beta/rc tags. +release = '0.5' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'fabtoolsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'fabtools.tex', u'fabtools Documentation', + u'Ronan Amicel', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'fabtools', u'fabtools Documentation', + [u'Ronan Amicel'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'fabtools', u'fabtools Documentation', + u'Ronan Amicel', 'fabtools', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..0500072 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,41 @@ +====================================== + Welcome to fabtools's documentation! +====================================== + +.. include:: ../README.rst + + +API Documentation +================= + +.. toctree:: + :maxdepth: 1 + + api/index + api/require/index + + +Changelog +========= + +.. toctree:: + :maxdepth: 1 + + CHANGELOG + + +Development +=========== + +.. toctree:: + :maxdepth: 1 + + tests + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/tests.rst b/docs/tests.rst new file mode 100644 index 0000000..98588ed --- /dev/null +++ b/docs/tests.rst @@ -0,0 +1,41 @@ +Tests +===== + +Running tests +------------- + +If you're using Python 2.7, you can launch the tests using the built-in `unittest <http://docs.python.org/library/unittest.html>`_ runner:: + + $ python -m unittest discover + +If you're using Python 2.5 or 2.6, you'll need to install `unittest2 <http://pypi.python.org/pypi/unittest2>`_, and use the provided runner:: + + $ pip install unittest2 + $ unit2 discover + +Or you can run the tests on all supported Python versions using `tox <http://pypi.python.org/pypi/tox>`_, which will take care of everything:: + + $ pip install tox + $ tox + +Unit tests +---------- + +Running unit tests requires the `mock <http://pypi.python.org/pypi/mock/>`_ library. + +Functional tests +---------------- + +Running functional tests requires `Vagrant <http://vagrantup.com/>`_ to launch virtual machines, +against which all the tests will be run. + +If Vagrant is not installed, the functional tests will be skipped automatically. + +If Vagrant is installed, the default is to run the tests on all available base boxes. +You can specify which base boxes should be used by setting the VAGRANT_BOXES environment variable:: + + $ VAGRANT_BOXES='ubuntu_10_04 ubuntu_12_04' tox -e py27 + +You can also use this to manually disable functional tests:: + + $ VAGRANT_BOXES='' tox diff --git a/fabtools/cron.py b/fabtools/cron.py index cfe0f3b..bf11455 100644 --- a/fabtools/cron.py +++ b/fabtools/cron.py @@ -1,5 +1,9 @@ """ -Fabric tools for managing crontab tasks +Cron tasks +========== + +This module provides tools to manage periodic tasks using cron. + """ from __future__ import with_statement @@ -11,7 +15,26 @@ from fabtools.files import upload_template def add_task(name, timespec, user, command): """ - Add a cron task + Add a cron task. + + The *command* will be run as *user* periodically. + + You can use any valid `crontab(5)`_ *timespec*, including the + ``@hourly``, ``@daily``, ``@weekly``, ``@monthly`` and ``@yearly`` + shortcuts. + + Examples:: + + from fabtools.cron import add_task + + # Run every month + add_task('cleanup', '@monthly', 'alice', '/home/alice/bin/cleanup.sh') + + # Run every tuesday and friday at 5:30am + add_task('reindex', '30 5 * * 2,4', 'bob', '/home/bob/bin/reindex.sh') + + .. _crontab(5): http://manpages.debian.net/cgi-bin/man.cgi?query=crontab&sektion=5 + """ with NamedTemporaryFile() as script: script.write('%(timespec)s %(user)s %(command)s\n' % locals()) @@ -25,6 +48,14 @@ def add_task(name, timespec, user, command): def add_daily(name, user, command): """ - Add a cron task to run daily + Shortcut to add a daily cron task. + + Example:: + + import fabtools + + # Run every day + fabtools.cron.add_daily('backup', 'root', '/usr/local/bin/backup.sh') + """ add_task(name, '@daily', user, command) diff --git a/fabtools/deb.py b/fabtools/deb.py index 7708724..fec3374 100644 --- a/fabtools/deb.py +++ b/fabtools/deb.py @@ -1,5 +1,10 @@ """ -Fabric tools for managing Debian/Ubuntu packages +Debian packages +=============== + +This module provides tools to manage Debian/Ubuntu packages +and repositories. + """ from __future__ import with_statement @@ -11,7 +16,7 @@ MANAGER = 'apt-get' def update_index(quiet=True): """ - Quietly update package index + Update APT package definitions. """ options = "-q -q" if quiet else "" sudo("%s %s update" % (MANAGER, options)) @@ -19,7 +24,7 @@ def update_index(quiet=True): def upgrade(safe=True): """ - Upgrade all packages + Upgrade all packages. """ manager = MANAGER cmds = {'apt-get': {False: 'dist-upgrade', True: 'upgrade'}, @@ -30,7 +35,7 @@ def upgrade(safe=True): def is_installed(pkg_name): """ - Check if .deb package is installed + Check if a package is installed. """ with settings(hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): res = run("dpkg -s %(pkg_name)s" % locals()) @@ -44,7 +49,26 @@ def is_installed(pkg_name): def install(packages, update=False, options=None): """ - Install .deb package(s) + Install one or more packages. + + If *update* is ``True``, the package definitions will be updated + first, using :py:func:`~fabtools.deb.update_index`. + + Extra *options* may be passed to ``apt-get`` if necessary. + + Example:: + + import fabtools + + # Update index, then install a single package + fabtools.deb.install('build-essential', update=True) + + # Install multiple packages + fabtools.deb.install([ + 'python-dev', + 'libxml2-dev', + ]) + """ manager = MANAGER if update: @@ -60,7 +84,12 @@ def install(packages, update=False, options=None): def uninstall(packages, purge=False, options=None): """ - Remove .deb package(s) + Remove one or more packages. + + If *purge* is ``True``, the package configuration files will be + removed from the system. + + Extra *options* may be passed to ``apt-get`` if necessary. """ manager = MANAGER command = "purge" if purge else "remove" @@ -75,7 +104,21 @@ def uninstall(packages, purge=False, options=None): def preseed_package(pkg_name, preseed): """ - Enable unattended package installation by preseeding debconf parameters + Enable unattended package installation by preseeding ``debconf`` + parameters. + + Example:: + + import fabtools + + # Unattended install of Postfix mail server + fabtools.deb.preseed_package('postfix', { + 'postfix/main_mailer_type': ('select', 'Internet Site'), + 'postfix/mailname': ('string', 'example.com'), + 'postfix/destinations': ('string', 'example.com, localhost.localdomain, localhost'), + }) + fabtools.deb.install('postfix') + """ for q_name, _ in preseed.items(): q_type, q_answer = _ @@ -84,8 +127,9 @@ def preseed_package(pkg_name, preseed): def get_selections(): """ - Get the state of dkpg selections - Returns dict with state => [packages] + Get the state of ``dkpg`` selections. + + Returns a dict with state => [packages]. """ with settings(hide('stdout')): res = sudo('dpkg --get-selections') @@ -98,7 +142,15 @@ def get_selections(): def distrib_codename(): """ - Get the codename of the distrib + Get the codename of the distrib. + + Example:: + + from fabtools.deb import distrib_codename + + if distrib_codename() == 'precise': + print('Ubuntu 12.04 LTS') + """ with settings(hide('running', 'stdout')): return run('lsb_release --codename --short') @@ -106,7 +158,19 @@ def distrib_codename(): def add_apt_key(filename, update=True): """ - Add an APT key + Trust packages signed with this public key. + + Example:: + + import fabtools + + # Download 3rd party APT public key + if not fabtools.is_file('rabbitmq-signing-key-public.asc'): + run('wget http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + + # Tell APT to trust that key + fabtools.deb.add_apt_key('rabbitmq-signing-key-public.asc') + """ sudo('apt-key add %(filename)s' % locals()) if update: diff --git a/fabtools/files.py b/fabtools/files.py index 9177ad6..c90eca8 100644 --- a/fabtools/files.py +++ b/fabtools/files.py @@ -1,5 +1,6 @@ """ -Fabric tools for managing files and directories +Files and directories +===================== """ from __future__ import with_statement @@ -11,7 +12,7 @@ from fabric.contrib.files import upload_template as _upload_template def is_file(path, use_sudo=False): """ - Check if a path exists, and is a file + Check if a path exists, and is a file. """ func = use_sudo and sudo or run with settings(hide('running', 'warnings'), warn_only=True): @@ -20,7 +21,7 @@ def is_file(path, use_sudo=False): def is_dir(path, use_sudo=False): """ - Check if a path exists, and is a directory + Check if a path exists, and is a directory. """ func = use_sudo and sudo or run with settings(hide('running', 'warnings'), warn_only=True): @@ -29,7 +30,7 @@ def is_dir(path, use_sudo=False): def is_link(path, use_sudo=False): """ - Check if a path exists, and is a symbolic link + Check if a path exists, and is a symbolic link. """ func = use_sudo and sudo or run with settings(hide('running', 'warnings'), warn_only=True): @@ -39,7 +40,7 @@ def is_link(path, use_sudo=False): def upload_template(filename, template, context=None, use_sudo=False, user="root", mkdir=False, chown=False): """ - Upload a template file + Upload a template file. """ if mkdir: d = os.path.dirname(filename) @@ -55,7 +56,7 @@ def upload_template(filename, template, context=None, use_sudo=False, def md5sum(filename, use_sudo=False): """ - Compute MD5 sum + Compute the MD5 sum of a file. """ func = use_sudo and sudo or run with settings(hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): @@ -68,37 +69,46 @@ def md5sum(filename, use_sudo=False): class watch(object): """ - Context manager to watch for changes to the contents of some files + Context manager to watch for changes to the contents of some files. - The 'filenames' argument can be either a string (single filename) + The *filenames* argument can be either a string (single filename) or a list (multiple filenames). - You can read the 'changed' attribute at the end of the block to + You can read the *changed* attribute at the end of the block to check if the contents of any of the watched files has changed. - You can also provide a callback that will be called at the end of + You can also provide a *callback* that will be called at the end of the block if the contents of any of the watched files has changed. Example using an explicit check:: from fabric.contrib.files import comment, uncomment + from fabtools.files import watch from fabtools.services import restart + + # Edit configuration file with watch('/etc/daemon.conf') as config: uncomment('/etc/daemon.conf', 'someoption') comment('/etc/daemon.conf', 'otheroption') + + # Restart daemon if needed if config.changed: restart('daemon') - Example using a callback:: + Same example using a callback:: from functools import partial + from fabric.contrib.files import comment, uncomment + from fabtools.files import watch from fabtools.services import restart + with watch('/etc/daemon.conf', callback=partial(restart, 'daemon')): uncomment('/etc/daemon.conf', 'someoption') comment('/etc/daemon.conf', 'otheroption') + """ def __init__(self, filenames, callback=None, use_sudo=False): diff --git a/fabtools/mysql.py b/fabtools/mysql.py index e3d19d1..4e43255 100644 --- a/fabtools/mysql.py +++ b/fabtools/mysql.py @@ -1,5 +1,9 @@ """ -Fabric tools for managing MySQL users and databases +MySQL users and databases +========================= + +This module provides tools for creating MySQL users and databases. + """ from __future__ import with_statement @@ -8,14 +12,14 @@ from fabric.api import * def prompt_password(user='root'): """ - Ask MySQL password interactively + Ask MySQL password interactively. """ return prompt('Please enter password for MySQL user "%s":' % user) def _query(query, use_sudo=True, **kwargs): """ - Run a MySQL query + Run a MySQL query. """ func = use_sudo and sudo or run @@ -33,7 +37,7 @@ def _query(query, use_sudo=True, **kwargs): def user_exists(name, **kwargs): """ - Check if a MySQL user exists + Check if a MySQL user exists. """ with settings(hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): res = _query("use mysql; SELECT User FROM user WHERE User = '%(name)s';" % { @@ -44,7 +48,16 @@ def user_exists(name, **kwargs): def create_user(name, password, host='localhost', **kwargs): """ - Create a MySQL user + Create a MySQL user. + + Example:: + + import fabtools + + # Create DB user if it does not exist + if not fabtools.mysql.user_exists('dbuser'): + fabtools.mysql.create_user('dbuser', password='somerandomstring') + """ with settings(hide('running')): _query("CREATE USER '%(name)s'@'%(host)s' IDENTIFIED BY '%(password)s';" % { @@ -57,7 +70,7 @@ def create_user(name, password, host='localhost', **kwargs): def database_exists(name, **kwargs): """ - Check if a MySQL database exists + Check if a MySQL database exists. """ with settings(hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): res = _query("use mysql; SELECT Db FROM db WHERE Db = '%(name)s';" % { @@ -69,7 +82,16 @@ def database_exists(name, **kwargs): def create_database(name, owner=None, owner_host='localhost', charset='utf8', collate='utf8_general_ci', **kwargs): """ - Create a MySQL database + Create a MySQL database. + + Example:: + + import fabtools + + # Create DB if it does not exist + if not fabtools.mysql.database_exists('myapp'): + fabtools.mysql.create_database('myapp', owner='dbuser') + """ with settings(hide('running')): diff --git a/fabtools/network.py b/fabtools/network.py index 0ade487..76a4766 100644 --- a/fabtools/network.py +++ b/fabtools/network.py @@ -1,5 +1,6 @@ """ -Fabric tools for managing network +Network +======= """ from __future__ import with_statement @@ -8,11 +9,7 @@ from fabric.api import * def interfaces(): """ - Get the list of network interfaces - - >>> fabtools.network.interfaces() - ['eth0', 'eth1', 'lo'] - + Get the list of network interfaces. """ with settings(hide('running', 'stdout')): res = run('/sbin/ifconfig -s') @@ -21,10 +18,15 @@ def interfaces(): def address(interface): """ - Get the IPv4 address assigned to an interface + Get the IPv4 address assigned to an interface. + + Example:: - >>> fabtools.network.address('lo') - '127.0.0.1' + import fabtools + + # Print all configured IP addresses + for interface in fabtools.network.interfaces(): + print(fabtools.network.address(interface)) """ with settings(hide('running', 'stdout')): @@ -34,10 +36,15 @@ def address(interface): def nameservers(): """ - Get the list of nameserver addresses + Get the list of nameserver addresses. + + Example:: + + import fabtools - >>> fabtools.network.nameservers() - ['208.67.222.222', '208.67.220.220'] + # Check that all name servers are reachable + for ip in fabtools.network.nameservers(): + run('ping -c1 %s' % ip) """ with settings(hide('running', 'stdout')): diff --git a/fabtools/openvz/__init__.py b/fabtools/openvz/__init__.py index 1dc5d40..9abe98e 100644 --- a/fabtools/openvz/__init__.py +++ b/fabtools/openvz/__init__.py @@ -1,2 +1,14 @@ +""" +OpenVZ containers +================= + +This module provides high-level tools for managing OpenVZ_ templates +and containers. + +.. _OpenVZ: http://openvz.org/ + +.. warning:: The remote host needs a patched kernel with OpenVZ support. + +""" from fabtools.openvz.operations import * from fabtools.openvz.contextmanager import guest diff --git a/fabtools/openvz/container.py b/fabtools/openvz/container.py index 2107dc2..a92af48 100644 --- a/fabtools/openvz/container.py +++ b/fabtools/openvz/container.py @@ -1,12 +1,13 @@ """ -Fabric tools for managing OpenVZ containers +OpenVZ containers +================= """ from fabtools import openvz as vz class Container(object): """ - Object-oriented interface to OpenVZ containers + Object-oriented interface to OpenVZ containers. """ def __init__(self, ctid): @@ -19,31 +20,81 @@ class Container(object): return None def create(self, **kwargs): + """ + Create the container. + + Extra args are passed to :py:func:`fabtools.openvz.create`. + """ return vz.create(self.ctid, **kwargs) def destroy(self): + """ + Destroy the container. + """ return vz.destroy(self.ctid) def set(self, **kwargs): + """ + Set container parameters. + + Extra args are passed to :py:func:`fabtools.openvz.set`. + """ return vz.set(self.ctid, **kwargs) def start(self, **kwargs): + """ + Start the container. + + Extra args are passed to :py:func:`fabtools.openvz.start`. + """ return vz.start(self.ctid, **kwargs) def stop(self, **kwargs): + """ + Stop the container. + + Extra args are passed to :py:func:`fabtools.openvz.stop`. + """ return vz.stop(self.ctid, **kwargs) def restart(self, **kwargs): + """ + Restart the container. + + Extra args are passed to :py:func:`fabtools.openvz.restart`. + """ return vz.restart(self.ctid, **kwargs) def status(self): + """ + Get the container's status. + """ return vz.status(self.ctid) def running(self): + """ + Check if the container is running. + """ return vz.running(self.ctid) def exists(self): + """ + Check if the container exists. + """ return vz.exists(self.ctid) def exec2(self, command): + """ + Run a command inside the container. + + :: + + from fabtools.require.openvz import container + + with container('foo') as ct: + res = ct.exec2('hostname') + + .. warning:: the command will be run as **root**. + + """ return vz.exec2(self.ctid, command) diff --git a/fabtools/openvz/contextmanager.py b/fabtools/openvz/contextmanager.py index 3b31cd2..98b3108 100644 --- a/fabtools/openvz/contextmanager.py +++ b/fabtools/openvz/contextmanager.py @@ -1,5 +1,6 @@ """ -Fabric tools for managing OpenVZ containers +OpenVZ containers +================= """ from __future__ import with_statement @@ -34,11 +35,26 @@ import fabric.sftp @contextmanager def guest(name_or_ctid): """ - Context manager to run commands inside a guest container + Context manager to run commands inside a guest container. - Warning: commands executed with run() will be run as root - inside the container. Use sudo(command, user='foo') to run - them as an unpriviledged user. + Supported basic operations are: `run`_, `sudo`_ and `put`_. + + .. warning:: commands executed with ``run()`` will be run as + **root** inside the container. + Use ``sudo(command, user='foo')`` to run them as + an unpriviledged user. + Example:: + + from fabtools.openvz import guest + + with guest('foo'): + run('hostname') + sudo('whoami', user='alice') + put('files/hello.txt') + + .. _run: http://docs.fabfile.org/en/1.4.3/api/core/operations.html#fabric.operations.run + .. _sudo: http://docs.fabfile.org/en/1.4.3/api/core/operations.html#fabric.operations.sudo + .. _put: http://docs.fabfile.org/en/1.4.3/api/core/operations.html#fabric.operations.put """ # Monkey patch fabric operations diff --git a/fabtools/openvz/operations.py b/fabtools/openvz/operations.py index 743718c..2bfb1d4 100644 --- a/fabtools/openvz/operations.py +++ b/fabtools/openvz/operations.py @@ -1,7 +1,6 @@ """ -Fabric tools for managing OpenVZ containers - -The remote host needs a patched kernel with OpenVZ support +OpenVZ containers +================= """ from __future__ import with_statement @@ -11,7 +10,7 @@ from fabric.api import * def create(ctid, ostemplate=None, config=None, private=None, root=None, ipadd=None, hostname=None, **kwargs): """ - Create OpenVZ container + Create an OpenVZ container. """ return _vzctl('create', ctid, ostemplate=ostemplate, config=config, private=private, root=root, ipadd=ipadd, hostname=hostname, **kwargs) @@ -19,44 +18,47 @@ def create(ctid, ostemplate=None, config=None, private=None, def destroy(ctid_or_name): """ - Destroy OpenVZ container + Destroy the container. """ return _vzctl('destroy', ctid_or_name) def set(ctid_or_name, save=True, **kwargs): """ - Set OpenVZ container parameters + Set container parameters. """ return _vzctl('set', ctid_or_name, save=save, **kwargs) def start(ctid_or_name, wait=False, force=False, **kwargs): """ - Start OpenVZ container + Start the container. + + If *wait* is ``True``, wait until the container is up and running. - Warning: wait=True is broken with vzctl 3.0.24 on Debian 6.0 (squeeze) + .. warning:: ``wait=True`` is broken with vzctl 3.0.24 + on Debian 6.0 (*squeeze*) """ return _vzctl('start', ctid_or_name, wait=wait, force=force, **kwargs) def stop(ctid_or_name, fast=False, **kwargs): """ - Stop OpenVZ container + Stop the container. """ return _vzctl('stop', ctid_or_name, fast=fast, **kwargs) def restart(ctid_or_name, wait=True, force=False, fast=False, **kwargs): """ - Restart OpenVZ container + Restart the container. """ return _vzctl('restart', ctid_or_name, wait=wait, force=force, fast=fast, **kwargs) def status(ctid_or_name): """ - Show status of OpenVZ container + Get the status of the container. """ with settings(warn_only=True): return _vzctl('status', ctid_or_name) @@ -64,14 +66,14 @@ def status(ctid_or_name): def running(ctid_or_name): """ - Is the container running? + Check if the container is running. """ return status(ctid_or_name).split(' ')[4] == 'running' def exists(ctid_or_name): """ - Does the container exist? + Check if the container exists. """ with settings(hide('running', 'stdout', 'warnings'), warn_only=True): return status(ctid_or_name).succeeded @@ -79,7 +81,16 @@ def exists(ctid_or_name): def exec2(ctid_or_name, command): """ - Run command inside OpenVZ container + Run a command inside the container. + + :: + + import fabtools + + res = fabtools.openvz.exec2('foo', 'hostname') + + .. warning:: the command will be run as **root**. + """ return sudo("vzctl exec2 %s '%s'" % (ctid_or_name, command)) @@ -105,7 +116,24 @@ def _expand_args(**kwargs): def download_template(name=None, url=None): """ - Download an OpenVZ template + Download an OpenVZ template. + + Example:: + + from fabtools.openvz import download_template + + # Use custom OS template + download_template(url='http://example.com/templates/mybox.tar.gz') + + If no *url* is provided, the OS template will be downloaded from the + `download.openvz.org <http://download.openvz.org/template/precreated/>`_ + repository:: + + from fabtools.openvz import download_template + + # Use OS template from http://download.openvz.org/template/precreated/ + download_template('debian-6.0-x86_64') + """ if url is None: url = 'http://download.openvz.org/template/precreated/%s.tar.gz' % name @@ -116,7 +144,7 @@ def download_template(name=None, url=None): def list_ctids(): """ - Get the list of currently used CTIDs + Get the list of currently used CTIDs. """ with settings(hide('running', 'stdout')): res = sudo('vzlist -a -1') @@ -125,7 +153,7 @@ def list_ctids(): def get_available_ctid(): """ - Get an available CTID + Get an available CTID. """ current_ctids = list_ctids() if current_ctids: diff --git a/fabtools/postgres.py b/fabtools/postgres.py index febd445..05f3a75 100644 --- a/fabtools/postgres.py +++ b/fabtools/postgres.py @@ -1,5 +1,9 @@ """ -Fabric tools for managing PostgreSQL users and databases +PostgreSQL users and databases +============================== + +This module provides tools for creating PostgreSQL users and databases. + """ from __future__ import with_statement @@ -15,7 +19,7 @@ def _run_as_pg(command): def user_exists(name): """ - Check if a PostgreSQL user exists + Check if a PostgreSQL user exists. """ with settings(hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): res = _run_as_pg('''psql -t -A -c "SELECT COUNT(*) FROM pg_user WHERE usename = '%(name)s';"''' % locals()) @@ -24,14 +28,23 @@ def user_exists(name): def create_user(name, password): """ - Create a PostgreSQL user + Create a PostgreSQL user. + + Example:: + + import fabtools + + # Create DB user if it does not exist + if not fabtools.postgres.user_exists('dbuser'): + fabtools.postgres.create_user('dbuser', password='somerandomstring') + """ _run_as_pg('''psql -c "CREATE USER %(name)s WITH PASSWORD '%(password)s';"''' % locals()) def database_exists(name): """ - Check if a PostgreSQL database exists + Check if a PostgreSQL database exists. """ with settings(hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): return _run_as_pg('''psql -d %(name)s -c ""''' % locals()).succeeded @@ -39,7 +52,16 @@ def database_exists(name): def create_database(name, owner, template='template0', encoding='UTF8', locale='en_US.UTF-8'): """ - Create a PostgreSQL database + Create a PostgreSQL database. + + Example:: + + import fabtools + + # Create DB if it does not exist + if not fabtools.postgres.database_exists('myapp'): + fabtools.postgres.create_database('myapp', owner='dbuser') + """ _run_as_pg('''createdb --owner %(owner)s --template %(template)s --encoding=%(encoding)s\ --lc-ctype=%(locale)s --lc-collate=%(locale)s %(name)s''' % locals()) diff --git a/fabtools/python.py b/fabtools/python.py index 88e874c..aa6b92a 100644 --- a/fabtools/python.py +++ b/fabtools/python.py @@ -1,5 +1,13 @@ """ -Fabric tools for managing Python packages using pip +Python environments and packages +================================ + +This module includes tools for using `virtual environments`_ +and installing packages using `pip`_. + +.. _virtual environments: http://www.virtualenv.org/ +.. _pip: http://www.pip-installer.org/ + """ from __future__ import with_statement @@ -14,7 +22,9 @@ from fabric.utils import puts def is_pip_installed(version=None): """ - Check if pip is installed + Check if `pip`_ is installed. + + .. _pip: http://www.pip-installer.org/ """ with settings(hide('running', 'warnings', 'stderr', 'stdout'), warn_only=True): res = run('pip --version') @@ -33,7 +43,17 @@ def is_pip_installed(version=None): def install_pip(): """ - Install pip + Install the latest version of `pip`_. + + .. _pip: http://www.pip-installer.org/ + + :: + + import fabtools + + if not fabtools.python.is_pip_installed(): + fabtools.python.install_pip() + """ with cd('/tmp'): run('curl --silent -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py') @@ -42,7 +62,7 @@ def install_pip(): def is_installed(package): """ - Check if a Python package is installed + Check if a Python package is installed. """ options = [] options = ' '.join(options) @@ -54,7 +74,20 @@ def is_installed(package): def install(packages, upgrade=False, use_mirrors=True, use_sudo=False, user=None, download_cache=None): """ - Install Python packages + Install Python package(s) using `pip`_. + + .. _pip: http://www.pip-installer.org/ + + Examples:: + + import fabtools + + # Install a single package + fabtools.python.install('package', use_sudo=True) + + # Install a list of packages + fabtools.python.install(['pkg1', 'pkg2'], use_sudo=True) + """ if not isinstance(packages, basestring): packages = ' '.join(packages) @@ -75,7 +108,15 @@ def install(packages, upgrade=False, use_mirrors=True, use_sudo=False, user=None def install_requirements(filename, upgrade=False, use_mirrors=True, use_sudo=False, user=None, download_cache=None): """ - Install Python packages from a pip requirements file + Install Python packages from a pip `requirements file`_. + + :: + + import fabtools + + fabtools.python.install_requirements('project/requirements.txt') + + .. _requirements file: http://www.pip-installer.org/en/latest/requirements.html """ options = [] if use_mirrors: @@ -95,7 +136,17 @@ def install_requirements(filename, upgrade=False, use_mirrors=True, use_sudo=Fal @contextmanager def virtualenv(directory, local=False): """ - Context manager to activate a Python virtualenv + Context manager to activate an existing Python `virtual environment`_. + + :: + + from fabric.api import run + from fabtools.python import virtualenv + + with virtualenv('/path/to/virtualenv'): + run('python -V') + + .. _virtual environment: http://www.virtualenv.org/ """ join = os.path.join if local else posixpath.join with prefix('. "%s"' % join(directory, 'bin', 'activate')): diff --git a/fabtools/python_distribute.py b/fabtools/python_distribute.py index 0293e9c..f5aedb0 100644 --- a/fabtools/python_distribute.py +++ b/fabtools/python_distribute.py @@ -1,5 +1,12 @@ """ -Fabric tools for managing Python packages using distribute +Python packages +=============== + +This module provides tools for installing Python packages using +the ``easy_install`` command provided by `distribute`_. + +.. _distribute: http://packages.python.org/distribute/ + """ from __future__ import with_statement @@ -8,7 +15,9 @@ from fabric.api import * def is_distribute_installed(): """ - Check if distribute is installed + Check if `distribute`_ is installed. + + .. _distribute: http://packages.python.org/distribute/ """ with settings(hide('running', 'warnings', 'stderr', 'stdout'), warn_only=True): res = run('easy_install --version') @@ -17,7 +26,17 @@ def is_distribute_installed(): def install_distribute(): """ - Install distribute + Install the latest version of `distribute`_. + + .. _distribute: http://packages.python.org/distribute/ + + :: + + from fabtools.python_distribute import * + + if not is_distribute_installed(): + install_distribute() + """ with cd("/tmp"): run("curl --silent -O http://python-distribute.org/distribute_setup.py") @@ -26,7 +45,22 @@ def install_distribute(): def install(packages, upgrade=False, use_sudo=False): """ - Install Python packages with distribute + Install Python packages with ``easy_install``. + + Examples:: + + import fabtools + + # Install a single package + fabtools.python_distribute.install('package', use_sudo=True) + + # Install a list of packages + fabtools.python_distribute.install(['pkg1', 'pkg2'], use_sudo=True) + + .. note:: most of the time, you'll want to use + :py:func:`fabtools.python.install()` instead, + which uses ``pip`` to install packages. + """ func = use_sudo and sudo or run if not isinstance(packages, basestring): diff --git a/fabtools/require/deb.py b/fabtools/require/deb.py index 933aaf4..0dfde24 100644 --- a/fabtools/require/deb.py +++ b/fabtools/require/deb.py @@ -1,5 +1,10 @@ """ -Idempotent API for managing Debian/Ubuntu packages +Debian packages +=============== + +This module provides high-level tools for managing Debian/Ubuntu packages +and repositories. + """ from __future__ import with_statement @@ -12,7 +17,15 @@ import fabtools.require def source(name, uri, distribution, *components): """ - Require a package source + Require a package source. + + :: + + from fabtools import require + + # Official MongoDB packages + require.deb.source('mongodb', 'http://downloads-distro.mongodb.org/repo/ubuntu-upstart', 'dist', '10gen') + """ path = '/etc/apt/sources.list.d/%(name)s.list' % locals() components = ' '.join(components) @@ -26,7 +39,16 @@ def source(name, uri, distribution, *components): def ppa(name): """ - Require a PPA + Require a `PPA`_ package source. + + :: + + from fabtools import require + + # Node.js packages by Chris Lea + require.deb.ppa('ppa:chris-lea/node.js') + + .. _PPA: https://help.launchpad.net/Packaging/PPA """ assert name.startswith('ppa:') user, repo = name[4:].split('/', 2) @@ -41,7 +63,13 @@ def ppa(name): def package(pkg_name, update=False): """ - Require a deb package + Require a deb package to be installed. + + :: + + from fabtools import require + + require.deb.package('foo') """ if not is_installed(pkg_name): install(pkg_name, update) @@ -49,7 +77,17 @@ def package(pkg_name, update=False): def packages(pkg_list, update=False): """ - Require several deb packages + Require several deb packages to be installed. + + :: + + from fabtools import require + + require.deb.packages([ + 'foo', + 'bar', + 'baz', + ]) """ pkg_list = [pkg for pkg in pkg_list if not is_installed(pkg)] if pkg_list: @@ -58,7 +96,13 @@ def packages(pkg_list, update=False): def nopackage(pkg_name): """ - Require a deb package to be absent + Require a deb package to be uninstalled. + + :: + + from fabtools import require + + require.deb.nopackage('apache2') """ if is_installed(pkg_name): uninstall(pkg_name) @@ -66,7 +110,17 @@ def nopackage(pkg_name): def nopackages(pkg_list): """ - Require several deb packages to be absent + Require several deb packages to be uninstalled. + + :: + + from fabtools import require + + require.deb.nopackages([ + 'perl', + 'php5', + 'ruby', + ]) """ pkg_list = [pkg for pkg in pkg_list if is_installed(pkg)] if pkg_list: diff --git a/fabtools/require/files.py b/fabtools/require/files.py index 23bace3..637c529 100644 --- a/fabtools/require/files.py +++ b/fabtools/require/files.py @@ -1,5 +1,10 @@ """ -Idempotent API for managing files and directories +Files and directories +===================== + +This module provides high-level tools for managing files and +directories. + """ from __future__ import with_statement @@ -17,7 +22,17 @@ BLOCKSIZE = 2 ** 20 # 1MB def directory(path, use_sudo=False, owner='', group='', mode=''): """ - Require a directory + Require a directory to exist. + + :: + + from fabtools import require + + require.directory('/tmp/mydir', owner='alice') + + .. note:: this function can be accessed directly from the + ``fabtools.require`` module for convenience. + """ func = use_sudo and sudo or run if not is_dir(path): @@ -31,17 +46,39 @@ def directory(path, use_sudo=False, owner='', group='', mode=''): def file(path=None, contents=None, source=None, url=None, md5=None, use_sudo=False, owner=None, group='', mode=None, verify_remote=True): """ - Require a file + Require a file to exist and have specific contents and properties. You can provide either: - - contents: the required contents of the file - - source: the filename of a local file to upload - - url: the address of a file to download (path is optional) - - If verify_remote is True (the default), then an MD5 comparison will be used - to check whether the remote file is the same as the source. If this is - False, the file will be assumed to be the same if it is present. This is - useful for very large files, where generating an MD5 sum may take a while. + + - *contents*: the required contents of the file:: + + from fabtools import require + + require.file('/tmp/hello.txt', contents='Hello, world') + + - *source*: the local path of a file to upload:: + + from fabtools import require + + require.file('/tmp/hello.txt', source='files/hello.txt') + + - *url*: the URL of a file to download (*path* is then optional):: + + from fabric.api import cd + from fabtools import require + + with cd('tmp'): + require.file(url='http://example.com/files/hello.txt') + + If *verify_remote* is ``True`` (the default), then an MD5 comparison + will be used to check whether the remote file is the same as the + source. If this is ``False``, the file will be assumed to be the + same if it is present. This is useful for very large files, where + generating an MD5 sum may take a while. + + .. note:: this function can be accessed directly from the + ``fabtools.require`` module for convenience. + """ func = use_sudo and sudo or run @@ -109,7 +146,7 @@ def file(path=None, contents=None, source=None, url=None, md5=None, def template_file(path=None, template_contents=None, template_source=None, context=None, **kwargs): """ - Require a file whose contents is defined by a template + Require a file whose contents is defined by a template. """ if template_contents is None: with open(template_source) as template_file: diff --git a/fabtools/require/mysql.py b/fabtools/require/mysql.py index 3e0e978..bceaca8 100644 --- a/fabtools/require/mysql.py +++ b/fabtools/require/mysql.py @@ -1,5 +1,10 @@ """ -Idempotent API for managing MySQL users and databases +MySQL +===== + +This module provides high-level tools for installing a MySQL server +and creating MySQL users and databases. + """ from __future__ import with_statement @@ -11,7 +16,14 @@ from fabtools.require.service import started def server(version=None, password=None): """ - Require a MySQL server + Require a MySQL server to be installed and running. + + Example:: + + from fabtools import require + + require.mysql.server(password='s3cr3t') + """ if version: pkg_name = 'mysql-server-%s' % version @@ -35,7 +47,16 @@ def server(version=None, password=None): def user(name, password, **kwargs): """ - Require a MySQL user + Require a MySQL user. + + Extra arguments will be passed to :py:func:`fabtools.mysql.create_user`. + + Example:: + + from fabtools import require + + require.mysql.user('dbuser', 'somerandomstring') + """ if not user_exists(name, **kwargs): create_user(name, password, **kwargs) @@ -43,7 +64,16 @@ def user(name, password, **kwargs): def database(name, **kwargs): """ - Require a MySQL database + Require a MySQL database. + + Extra arguments will be passed to :py:func:`fabtools.mysql.create_database`. + + Example:: + + from fabtools import require + + require.mysql.database('myapp', owner='dbuser') + """ if not database_exists(name, **kwargs): create_database(name, **kwargs) diff --git a/fabtools/require/nginx.py b/fabtools/require/nginx.py index 52ab307..cc24c62 100644 --- a/fabtools/require/nginx.py +++ b/fabtools/require/nginx.py @@ -1,5 +1,12 @@ """ -Idempotent API for managing nginx sites +Nginx +===== + +This module provides high-level tools for installing the `nginx`_ +web server and managing the configuration of web sites. + +.. nginx: http://nginx.org/ + """ from __future__ import with_statement @@ -13,7 +20,13 @@ from fabtools.require.service import started def server(): """ - Require an nginx server + Require nginx server to be installed and running. + + :: + + from fabtools import require + + require.nginx.server() """ package('nginx') started('nginx') @@ -21,7 +34,31 @@ def server(): def site(server_name, template_contents=None, template_source=None, enabled=True, check_config=True, **kwargs): """ - Require an nginx site + Require an nginx site. + + You must provide a template for the site configuration, either as a + string (*template_contents*) or as the path to a local template + file (*template_source*). + + :: + + from fabtools import require + + CONFIG_TPL = ''' + server { + listen %(port)d; + server_name %(server_name)s %(server_alias)s; + root %(docroot)s; + access_log /var/log/nginx/%(server_name)s.log; + }''' + + require.nginx.site('example.com', template_contents=CONFIG_TPL, + port=80, + server_alias='www.example.com', + docroot='/var/www/mysite', + ) + + .. seealso:: :py:func:`fabtools.require.files.template_file` """ server() @@ -79,6 +116,25 @@ server { def proxied_site(server_name, enabled=True, **kwargs): """ - Require an nginx site for a proxied app + Require an nginx site for a proxied app. + + This uses a predefined configuration template suitable for proxying + requests to a backend application server. + + Required keyword arguments are: + + - *port*: the port nginx should listen on + - *proxy_url*: URL of backend application server + - *docroot*: path to static files + + :: + + from fabtools import require + + require.nginx.proxied_site('example.com', + port=80, + proxy_url='http://127.0.0.1:8080/', + docroot='/path/to/myapp/static', + ) """ site(server_name, template_contents=PROXIED_SITE_TEMPLATE, enabled=enabled, **kwargs) diff --git a/fabtools/require/openvz.py b/fabtools/require/openvz.py index ab7d121..bf586c7 100644 --- a/fabtools/require/openvz.py +++ b/fabtools/require/openvz.py @@ -1,5 +1,14 @@ """ -Idempotent API for managing OpenVZ containers +OpenVZ containers +================= + +This module provides high-level tools for managing OpenVZ_ templates +and containers. + +.. _OpenVZ: http://openvz.org/ + +.. warning:: The remote host needs a patched kernel with OpenVZ support. + """ import os.path @@ -10,7 +19,26 @@ from fabtools.openvz.container import Container def template(name=None, url=None): """ - Require an OpenVZ template + Require an OpenVZ OS template. + + If the OS template is not installed yet, it will be downloaded from + *url* using :py:func:`~fabtools.openvz.download_template()`:: + + from fabtools import require + + # Use custom OS template + require.openvz.template(url='http://example.com/templates/mybox.tar.gz') + + If no *url* is provided, :py:func:`~fabtools.openvz.download_template()` + will attempt to download the OS template from the + `download.openvz.org <http://download.openvz.org/template/precreated/>`_ + repository:: + + from fabtools import require + + # Use OS template from http://download.openvz.org/template/precreated/ + require.openvz.template('debian-6.0-x86_64') + """ if name is not None: filename = '%s.tar.gz' % name @@ -23,7 +51,37 @@ def template(name=None, url=None): def container(name, ostemplate, **kwargs): """ - Require an OpenVZ container + Require an OpenVZ container. + + If it does not exist, the container will be created using the + specified OS template + (see :py:func:`fabtools.require.openvz.template()`). + + Extra args will be passed to :py:func:`fabtools.openvz.create()`:: + + from fabtools import require + + require.openvz.container('foo', 'debian', ipadd='1.2.3.4') + + This function returns a :py:class:`fabtools.openvz.Container` + object, that can be used to perform further operations:: + + from fabtools.require.openvz import container + + ct = container('foo', 'debian') + ct.set('ipadd', '1.2.3.4') + ct.start() + ct.exec2('hostname') + + This function can also be used as a context manager:: + + from fabtools.require.openvz import container + + with container('foo', 'debian') as ct: + ct.set('ipadd', '1.2.3.4') + ct.start() + ct.exec2('hostname') + """ if not openvz.exists(name): ctid = openvz.get_available_ctid() diff --git a/fabtools/require/postfix.py b/fabtools/require/postfix.py index f5929ea..e7af201 100644 --- a/fabtools/require/postfix.py +++ b/fabtools/require/postfix.py @@ -1,5 +1,11 @@ """ -Idempotent API for managing Postfix email server +Postfix +======= + +This module provides high-level tools for managing the Postfix_ email server. + +.. _Postfix: http://www.postfix.org/ + """ from fabric.api import * from fabtools.deb import is_installed, preseed_package, install @@ -8,7 +14,17 @@ from fabtools.require.service import started def server(mailname): """ - Require a Postfix email server + Require a Postfix email server. + + This makes sure that Postfix is installed and started. + + :: + + from fabtools import require + + # Handle incoming email for our domain + require.postfix.server('example.com') + """ # Ensure the package is installed diff --git a/fabtools/require/postgres.py b/fabtools/require/postgres.py index fa3d97d..6a0be8e 100644 --- a/fabtools/require/postgres.py +++ b/fabtools/require/postgres.py @@ -1,5 +1,6 @@ """ -Idempotent API for managing PostgreSQL users and databases +PostgreSQL users and databases +============================== """ from __future__ import with_statement @@ -13,7 +14,14 @@ from fabtools.require.service import started def server(version=None): """ - Require a PostgreSQL server + Require a PostgreSQL server to be installed and running. + + :: + + from fabtools import require + + require.postgres.server() + """ if version: pkg_name = 'postgresql-%s' % version @@ -35,7 +43,14 @@ def server(version=None): def user(name, password): """ - Require a PostgreSQL user + Require a PostgreSQL user. + + :: + + from fabtools import require + + require.postgres.user('dbuser', password='somerandomstring') + """ if not user_exists(name): create_user(name, password) @@ -44,7 +59,14 @@ def user(name, password): def database(name, owner, template='template0', encoding='UTF8', locale='en_US.UTF-8'): """ - Require a PostgreSQL database + Require a PostgreSQL database. + + :: + + from fabtools import require + + require.postgres.database('myapp', owner='dbuser') + """ if not database_exists(name): create_database(name, owner, template=template, encoding=encoding, diff --git a/fabtools/require/python.py b/fabtools/require/python.py index 41ea0bf..cc47247 100644 --- a/fabtools/require/python.py +++ b/fabtools/require/python.py @@ -1,5 +1,13 @@ """ -Idempotent API for managing Python packages +Python environments and packages +================================ + +This module includes tools for using `virtual environments`_ +and installing packages using `pip`_. + +.. _virtual environments: http://www.virtualenv.org/ +.. _pip: http://www.pip-installer.org/ + """ import posixpath @@ -11,7 +19,9 @@ from fabtools.require import deb def distribute(): """ - Require distribute + Require `distribute`_ to be installed. + + .. _distribute: http://packages.python.org/distribute/ """ deb.packages([ 'curl', @@ -23,7 +33,7 @@ def distribute(): def pip(version=None): """ - Require pip + Require `pip`_ to be installed. """ distribute() if not is_pip_installed(version): @@ -32,7 +42,24 @@ def pip(version=None): def package(pkg_name, url=None, **kwargs): """ - Require a Python package + Require a Python package. + + If the package is not installed, it will be installed + using the `pip installer`_. + + :: + + from fabtools.python import virtualenv + from fabtools import require + + # Install package system-wide + require.python.package('foo', use_sudo=True) + + # Install package in an existing virtual environment + with virtualenv('/path/to/venv'): + require.python.package('bar') + + .. _pip installer: http://www.pip-installer.org/ """ pip('1.1') if not is_installed(pkg_name): @@ -41,7 +68,7 @@ def package(pkg_name, url=None, **kwargs): def packages(pkg_list, **kwargs): """ - Require several Python packages + Require several Python packages. """ pip('1.1') pkg_list = [pkg for pkg in pkg_list if not is_installed(pkg)] @@ -51,7 +78,9 @@ def packages(pkg_list, **kwargs): def requirements(filename, **kwargs): """ - Require Python packages from a pip requirements file + Require Python packages from a pip `requirements file`_. + + .. _requirements file: http://www.pip-installer.org/en/latest/requirements.html """ pip('1.1') install_requirements(filename, **kwargs) @@ -59,7 +88,15 @@ def requirements(filename, **kwargs): def virtualenv(directory, system_site_packages=False, python=None, use_sudo=False, user=None): """ - Require a Python virtual environment + Require a Python `virtual environment`_. + + :: + + from fabtools import require + + require.python.virtualenv('/path/to/venv') + + .. _virtual environment: http://www.virtualenv.org/ """ package('virtualenv', use_sudo=True) if not is_file(posixpath.join(directory, 'bin', 'python')): diff --git a/fabtools/require/redis.py b/fabtools/require/redis.py index cc9041c..d57b042 100644 --- a/fabtools/require/redis.py +++ b/fabtools/require/redis.py @@ -1,5 +1,11 @@ """ -Idempotent API for managing Redis instances +Redis +===== + +This module provides high-level tools for managing `Redis`_ instances. + +.. _Redis: http://redis.io/ + """ from __future__ import with_statement @@ -20,7 +26,9 @@ BINARIES = [ def installed_from_source(version=VERSION): """ - Require Redis to be installed from source + Require Redis to be installed from source. + + The compiled binaries will be installed in ``/opt/redis-{version}/``. """ from fabtools import require @@ -50,9 +58,28 @@ def installed_from_source(version=VERSION): def instance(name, version=VERSION, **kwargs): """ - Require a Redis instance to be running + Require a Redis instance to be running. + + The instance will be managed using supervisord, as a process named + ``redis_{name}``, running as the ``redis`` user. + + :: + + from fabtools import require + from fabtools.supervisor import process_status + + require.redis.installed_from_source() + + require.redis.instance('db1', port='6379') + require.redis.instance('db2', port='6380') + + print process_status('redis_db1') + print process_status('redis_db2') + + .. seealso:: :ref:`supervisor_module` and + :ref:`require_supervisor_module` + - The instance will be managed using supervisord. """ from fabtools import require diff --git a/fabtools/require/service.py b/fabtools/require/service.py index b268972..75bcc15 100644 --- a/fabtools/require/service.py +++ b/fabtools/require/service.py @@ -1,21 +1,57 @@ """ -Idempotent API for managing services +System services +=============== + +This module provides high-level tools for managing system services. +The underlying operations use the ``service`` command, allowing to +support both `upstart`_ services and traditional SysV-style +``/etc/init.d/`` scripts. + +.. _upstart: http://upstart.ubuntu.com/ + """ from fabric.api import * from fabtools.service import * def started(service): + """ + Require a service to be started. + + :: + + from fabtools import require + + require.service.started('foo') + """ if not is_running(service): start(service) def stopped(service): + """ + Require a service to be stopped. + + :: + + from fabtools import require + + require.service.stopped('foo') + """ if is_running(service): stop(service) def restarted(service): + """ + Require a service to be restarted. + + :: + + from fabtools import require + + require.service.restarted('foo') + """ if is_running(service): restart(service) else: diff --git a/fabtools/require/shorewall.py b/fabtools/require/shorewall.py index 51830d8..104548d 100644 --- a/fabtools/require/shorewall.py +++ b/fabtools/require/shorewall.py @@ -1,5 +1,6 @@ """ -Idempotent API for managing shorewall +Shorewall firewall +================== """ from __future__ import with_statement @@ -245,7 +246,25 @@ CONFIG_FILES = [ def firewall(zones=None, interfaces=None, policy=None, rules=None, routestopped=None, masq=None): """ - Require a firewall + Ensure that a firewall is configured. + + Example:: + + from fabtools.shorewall import * + from fabtools import require + + # We need a firewall with some custom rules + require.shorewall.firewall( + rules=[ + Ping(), + SSH(), + HTTP(), + HTTPS(), + SMTP(), + rule(port=1234, source=hosts(['example.com'])), + ] + ) + """ package('shorewall') @@ -268,7 +287,7 @@ def firewall(zones=None, interfaces=None, policy=None, rules=None, def started(): """ - Ensure the firewall is started + Ensure that the firewall is started. """ if not is_started(): start('shorewall') @@ -276,7 +295,7 @@ def started(): def stopped(): """ - Ensure the firewall is stopped + Ensure that the firewall is stopped. """ if not is_stopped(): stop('shorewall') diff --git a/fabtools/require/supervisor.py b/fabtools/require/supervisor.py index 07b6553..fc1bb91 100644 --- a/fabtools/require/supervisor.py +++ b/fabtools/require/supervisor.py @@ -1,5 +1,12 @@ """ -Idempotent API for managing supervisor processes +Supervisor processes +==================== + +This module provides high-level tools for managing long-running +processes using `supervisord`_. + +.. _supervisord: http://supervisord.org/ + """ from __future__ import with_statement @@ -9,7 +16,37 @@ from fabtools.supervisor import update_config, process_status, start_process def process(name, **kwargs): """ - Require a supervisor process + Require a supervisor process to be running. + + Keyword arguments will be used to build the program configuration + file. Some useful arguments are: + + - ``command``: complete command including arguments (**required**) + - ``directory``: absolute path to the working directory + - ``user``: run the process as this user + - ``stdout_logfile``: absolute path to the log file + + You should refer to the `supervisord documentation`_ for the + complete list of allowed arguments. + + .. note:: the default values for the following arguments differs from + the supervisord defaults: + + - ``autorestart``: defaults to ``true`` + - ``redirect_stderr``: defaults to ``true`` + + Example:: + + from fabtools import require + + require.supervisor.process('myapp', + command='/path/to/venv/bin/myapp --config production.ini --someflag', + directory='/path/to/working/dir', + user='alice', + stdout_logfile='/path/to/logs/myapp.log', + ) + + .. _supervisord documentation: http://supervisord.org/configuration.html#program-x-section-values """ from fabtools import require require.deb.package('supervisor') diff --git a/fabtools/require/system.py b/fabtools/require/system.py index d798fbd..bb1a651 100644 --- a/fabtools/require/system.py +++ b/fabtools/require/system.py @@ -1,5 +1,6 @@ """ -Idempotent API for managing system settings +System settings +=============== """ from __future__ import with_statement @@ -16,7 +17,7 @@ from fabtools.system import ( def sysctl(key, value, persist=True): """ - Require a kernel parameter to have a specific value + Require a kernel parameter to have a specific value. """ if get_sysctl(key) != value: set_sysctl(key, value) @@ -34,7 +35,7 @@ def sysctl(key, value, persist=True): def hostname(name): """ - Require the hostname to have a specific value + Require the hostname to have a specific value. """ if get_hostname() != name: set_hostname(name) @@ -42,7 +43,7 @@ def hostname(name): def locales(names): """ - Require the list of locales to be available + Require the list of locales to be available. """ config_file = '/var/lib/locales/supported.d/local' @@ -66,14 +67,14 @@ def locales(names): def locale(name): """ - Require the locale to be available + Require the locale to be available. """ locales([name]) def default_locale(name): """ - Require the locale to be the default + Require the locale to be the default. """ from fabtools import require diff --git a/fabtools/require/users.py b/fabtools/require/users.py index 074ac27..6e1157c 100644 --- a/fabtools/require/users.py +++ b/fabtools/require/users.py @@ -1,5 +1,6 @@ """ -Idempotent API for managing users +System users +============ """ from fabtools.files import is_file from fabtools.user import * @@ -9,7 +10,18 @@ import fabtools.require def user(name, home=None): """ - Require a user + Require a user. + + :: + + from fabtools import require + + require.user('alice') # no home directory + require.user('bob', home='/home/bob') + + .. note:: this function can be accessed directly from the + ``fabtools.require`` module for convenience. + """ if not exists(name): create(name, home=home) @@ -19,7 +31,11 @@ def user(name, home=None): def sudoer(username, hosts="ALL", operators="ALL", passwd=False, commands="ALL"): """ - Require sudo permissions for a given user + Require sudo permissions for a given user. + + .. note:: this function can be accessed directly from the + ``fabtools.require`` module for convenience. + """ tags = "PASSWD:" if passwd else "NOPASSWD:" spec = "%(username)s %(hosts)s=(%(operators)s) %(tags)s %(commands)s" % locals() diff --git a/fabtools/service.py b/fabtools/service.py index 91a1235..faed919 100644 --- a/fabtools/service.py +++ b/fabtools/service.py @@ -1,8 +1,13 @@ """ -Fabric tools for managing services +System services +=============== -This uses the ``service`` command, supporting both upstart services +This module provides low-level tools for managing system services, +using the ``service`` command. It supports both `upstart`_ services and traditional SysV-style ``/etc/init.d/`` scripts. + +.. _upstart: http://upstart.ubuntu.com/ + """ from __future__ import with_statement @@ -11,7 +16,14 @@ from fabric.api import * def is_running(service): """ - Check if a service is running + Check if a service is running. + + :: + + import fabtools + + if fabtools.service.is_running('foo'): + print "Service foo is running!" """ with settings(hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): res = sudo('service %(service)s status' % locals()) @@ -20,20 +32,46 @@ def is_running(service): def start(service): """ - Start a service + Start a service. + + :: + + import fabtools + + # Start service if it is not running + if not fabtools.service.is_running('foo'): + fabtools.service.start('foo') """ sudo('service %(service)s start' % locals()) def stop(service): """ - Stop a service + Stop a service. + + :: + + import fabtools + + # Stop service if it is running + if fabtools.service.is_running('foo'): + fabtools.service.stop('foo') """ sudo('service %(service)s stop' % locals()) def restart(service): """ - Restart a service + Restart a service. + + :: + + import fabtools + + # Start service, or restart it if it is already running + if fabtools.service.is_running('foo'): + fabtools.service.restart('foo') + else: + fabtools.service.start('foo') """ sudo('service %(service)s restart' % locals()) diff --git a/fabtools/shorewall.py b/fabtools/shorewall.py index 16622f5..74bdcf3 100644 --- a/fabtools/shorewall.py +++ b/fabtools/shorewall.py @@ -1,5 +1,6 @@ """ -Fabric tools for managing shorewall +Shorewall firewall +================== """ from __future__ import with_statement @@ -11,7 +12,7 @@ import re def status(): """ - Get the firewall status + Get the firewall status. """ with settings(hide('running', 'stdout', 'warnings'), warn_only=True): res = sudo('shorewall status') @@ -20,21 +21,21 @@ def status(): def is_started(): """ - Returns True if shorewall is started + Check if the firewall is started. """ return status() == 'running' def is_stopped(): """ - Returns True if shorewall is stopped + Check if the firewall is stopped. """ return status() == 'stopped' def hosts(hostnames, zone='net'): """ - Builds a host list suitable for use in a firewall rule + Builds a host list suitable for use in a firewall rule. """ addresses = [gethostbyname(name) for name in hostnames] return "%s:%s" % (zone, ','.join(addresses)) @@ -42,7 +43,18 @@ def hosts(hostnames, zone='net'): def rule(port, action='ACCEPT', source='net', dest='$FW', proto='tcp'): """ - Builds a firewall rule + Helper to build a firewall rule. + + Examples:: + + from fabtools.shorewall import rule + + # Rule to accept connections from example.com on port 1234 + r1 = rule(port=1234, source=hosts(['example.com'])) + + # Rule to reject outgoing SMTP connections + r2 = rule(port=25, action='REJECT', source='$FW', dest='net') + """ return { 'action': action, @@ -55,34 +67,44 @@ def rule(port, action='ACCEPT', source='net', dest='$FW', proto='tcp'): def Ping(**kwargs): """ - Build a firewall rule for ICMP pings + Helper to build a firewall rule for ICMP pings. + + Extra args will be passed to :py:func:`~fabtools.shorewall.rule`. """ return rule(port=8, proto='icmp', **kwargs) def SSH(port=22, **kwargs): """ - Build a firewall rule for SSH connections + Helper to build a firewall rule for SSH connections + + Extra args will be passed to :py:func:`~fabtools.shorewall.rule`. """ return rule(port, **kwargs) def HTTP(port=80, **kwargs): """ - Builds a firewall rule for HTTP connections + Helper to build a firewall rule for HTTP connections + + Extra args will be passed to :py:func:`~fabtools.shorewall.rule`. """ return rule(port, **kwargs) def HTTPS(port=443, **kwargs): """ - Builds a firewall rule for HTTPS connections + Helper to build a firewall rule for HTTPS connections + + Extra args will be passed to :py:func:`~fabtools.shorewall.rule`. """ return rule(port, **kwargs) def SMTP(port=25, **kwargs): """ - Builds a firewall rule for SMTP connections + Helper to build a firewall rule for SMTP connections + + Extra args will be passed to :py:func:`~fabtools.shorewall.rule`. """ return rule(port, **kwargs) diff --git a/fabtools/supervisor.py b/fabtools/supervisor.py index bc13b2d..e7061a9 100644 --- a/fabtools/supervisor.py +++ b/fabtools/supervisor.py @@ -1,5 +1,12 @@ """ -Fabric tools for managing supervisor processes +Supervisor processes +==================== + +This module provides high-level tools for managing long-running +processes using `supervisord`_. + +.. _supervisord: http://supervisord.org/ + """ from __future__ import with_statement @@ -8,23 +15,24 @@ from fabric.api import * def reload_config(): """ - Reload supervisor configuration + Reload supervisor configuration. """ sudo("supervisorctl reload") def update_config(): """ - Reread and update supervisor job configurations. Less heavy-handed than - a full reload, as it doesn't restart the backend supervisor process and - all managed processes. + Reread and update supervisor job configurations. + + Less heavy-handed than a full reload, as it doesn't restart the + backend supervisor process and all managed processes. """ sudo("supervisorctl update") def process_status(name): """ - Get the status of a supervisor process + Get the status of a supervisor process. """ with settings(hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): res = sudo("supervisorctl status %(name)s" % locals()) diff --git a/fabtools/system.py b/fabtools/system.py index 9e233a5..0992c1f 100644 --- a/fabtools/system.py +++ b/fabtools/system.py @@ -1,5 +1,6 @@ """ -Fabric tools for managing system settings +System settings +=============== """ from __future__ import with_statement @@ -8,7 +9,7 @@ from fabric.api import * def get_hostname(): """ - Get the fully qualified hostname + Get the fully qualified hostname. """ with settings(hide('running', 'stdout')): return run('hostname --fqdn') @@ -16,7 +17,7 @@ def get_hostname(): def set_hostname(hostname, persist=True): """ - Set the hostname + Set the hostname. """ sudo('hostname %s' % hostname) if persist: @@ -25,7 +26,14 @@ def set_hostname(hostname, persist=True): def get_sysctl(key): """ - Get a kernel parameter + Get a kernel parameter. + + Example:: + + from fabtools.system import get_sysctl + + print "Max number of open files:", get_sysctl('fs.file-max') + """ with settings(hide('running', 'stdout')): return sudo('/sbin/sysctl -n -e %(key)s' % locals()) @@ -33,16 +41,24 @@ def get_sysctl(key): def set_sysctl(key, value): """ - Set a kernel parameter + Set a kernel parameter. + + Example:: + + import fabtools + + # Protect from SYN flooding attack + fabtools.system.set_sysctl('net.ipv4.tcp_syncookies', 1) + """ sudo('/sbin/sysctl -n -e -w %(key)s=%(value)s' % locals()) def supported_locales(): """ - Gets the list of supported locales + Gets the list of supported locales. - Each locale is returned as a (locale, charset) tuple + Each locale is returned as a ``(locale, charset)`` tuple. """ with settings(hide('running', 'stdout')): res = run('grep -v "^#" /usr/share/i18n/SUPPORTED') diff --git a/fabtools/user.py b/fabtools/user.py index 25d6ec4..de40148 100644 --- a/fabtools/user.py +++ b/fabtools/user.py @@ -1,5 +1,6 @@ """ -Fabric tools for managing users +Users +===== """ from __future__ import with_statement @@ -8,7 +9,7 @@ from fabric.api import * def exists(name): """ - Check if user exists + Check if a user exists. """ with settings(hide('running', 'stdout', 'warnings'), warn_only=True): return sudo('getent passwd %(name)s' % locals()).succeeded @@ -16,7 +17,15 @@ def exists(name): def create(name, home=None, shell=None, uid=None, gid=None, groups=None): """ - Create a new user + Create a new user. + + Example:: + + import fabtools + + if not fabtools.user.exists('alice'): + fabtools.user.create('alice', home='/home/alice') + """ options = [] if gid: diff --git a/fabtools/vagrant.py b/fabtools/vagrant.py index db9d2d6..59d487f 100644 --- a/fabtools/vagrant.py +++ b/fabtools/vagrant.py @@ -1,3 +1,7 @@ +""" +Vagrant helpers +=============== +""" from __future__ import with_statement from fabric.api import * @@ -5,7 +9,7 @@ from fabric.api import * def ssh_config(name=''): """ - Get the SSH parameters for a vagrant VM + Get the SSH parameters for connecting to a vagrant VM. """ with settings(hide('running')): output = local('vagrant ssh-config %s' % name, capture=True) @@ -36,7 +40,21 @@ def _settings_dict(config): @task def vagrant(name=''): """ - Run the following tasks on a vagrant box + Run the following tasks on a vagrant box. + + First, you need to import this task in your ``fabfile.py``:: + + from fabric.api import * + from fabtools.vagrant import vagrant + + @task + def some_task(): + run('echo hello') + + Then you can easily run tasks on your current Vagrant box:: + + $ fab vagrant some_task + """ config = ssh_config(name) @@ -47,7 +65,15 @@ def vagrant(name=''): def vagrant_settings(name='', *args, **kwargs): """ Context manager that sets a vagrant VM - as the remote host + as the remote host. + + Use this context manager inside a task to run commands + on your current Vagrant box:: + + from fabtools.vagrant import vagrant_settings + + with vagrant_settings(): + run('hostname') """ config = ssh_config(name) |