Skip to content
This repository has been archived by the owner on Dec 21, 2022. It is now read-only.

Commit

Permalink
Improved documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanslenders committed Feb 27, 2014
1 parent 4e66841 commit 98c7625
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 85 deletions.
4 changes: 2 additions & 2 deletions deployer/host/base.py
Expand Up @@ -71,8 +71,8 @@ def __exit__(context, *args):

def cd(self, path, expand=False):
"""
Execute commands in this directory.
Nesting of cd-statements is allowed.
Execute commands in this directory. Nesting of cd-statements is
allowed.
::
Expand Down
120 changes: 78 additions & 42 deletions deployer/host_container.py
@@ -1,5 +1,5 @@
from contextlib import nested
from deployer.host import Host, HostContext
from deployer.host import Host
from deployer.exceptions import ExecCommandFailed
from deployer.utils import isclass, esc1
from functools import wraps
Expand All @@ -9,18 +9,21 @@

class HostsContainer(object):
"""
Facade to the host instances.
if you have a role, name 'www' inside the service webserver, you can do:
- webserver.hosts.run(...)
- webserver.hosts.www.run(...)
- webserver.hosts[0].run(...)
- webserver.hosts.www[0].run(...)
- webserver.hosts.filter('www')[0].run(...)
The host container also keeps track of HostStatus. So, if we fork a new
thread, and the HostStatus object gets modified in either thread. Clone
this HostsContainer first.
Proxy to a group of :class:`~deployer.host.base.Host` instances.
For instance, if you have a role, name 'www' inside the container, you
could do:
::
host_container.run(...)
host_container[0].run(...)
host_container.filter('www')[0].run(...)
Typically, you get a :class:`~deployer.host_container.HostsContainer` class
by accessing the :attr:`~deployer.node.base.Env.hosts` property of an
:class:`~deployer.node.base.Env` (:class:`~deployer.node.base.Node`
wrapper.)
"""
def __init__(self, hosts, pty=None, logger=None, is_sandbox=False):
# the hosts parameter is a dictionary, mapping roles to <Host> instances, or lists
Expand Down Expand Up @@ -99,7 +102,7 @@ def get_hosts_as_dict(self):
Return a dictionary which maps all the roles to the set of
:class:`deployer.host.Host` classes for each role.
"""
return { k: { h.__class__ for h in l } for k,l in self._hosts.items() }
return { k: { h.__class__ for h in l } for k, l in self._hosts.items() }

def __repr__(self):
return ('<%s\n' % self.__class__.__name__ +
Expand Down Expand Up @@ -194,10 +197,12 @@ def __iter__(self):
def expand_path(self, path):
return [ h.expand_path(path) for h in self._all ]

@wraps(Host.run)
def run(self, *a, **kw):
"""
Call ``run`` with this parameters on every host.
"""run(command, sandbox=False, interactive=True, user=None, ignore_exit_status=False, initial_input=None)
Call :func:`~deployer.host.base.Host.run` with this parameters on every
:class:`~deployer.host.base.Host` in this container. It can be executed
in parallel when we have multiple hosts.
:returns: An array of all the results.
"""
Expand Down Expand Up @@ -235,40 +240,64 @@ def call(pty):
else:
return [ c(self._pty) for c in callables ]

@wraps(Host.sudo)
def sudo(self, *args, **kwargs):
"""
Call ``sudo`` with this parameters on every host.
"""sudo(command, sandbox=False, interactive=True, user=None, ignore_exit_status=False, initial_input=None)
Call :func:`~deployer.host.base.Host.sudo` with this parameters on every
:class:`~deployer.host.base.Host` in this container. It can be executed
in parallel when we have multiple hosts.
:returns: An array of all the results.
"""
kwargs['use_sudo'] = True
return HostsContainer.run(self, *args, **kwargs)
# NOTE: here we use HostsContainer instead of self, to be
# sure that we don't call te overriden method in
# HostContainer.

@wraps(HostContext.prefix)
def prefix(self, *a, **kw):
def prefix(self, command):
"""
Call 'prefix' with this parameters on every host.
Call :func:`~deployer.host.base.HostContext.prefix` on the
:class:`~deployer.host.base.HostContext` of every host.
::
with host.prefix('workon environment'):
host.run('./manage.py migrate')
"""
return nested(* [ h.host_context.prefix(*a, **kw) for h in self._all ])
return nested(* [ h.host_context.prefix(command) for h in self._all ])

@wraps(HostContext.cd)
def cd(self, *a, **kw):
def cd(self, path, expand=False):
"""
Call 'cd' with this parameters on every host.
Execute commands in this directory. Nesting of cd-statements is
allowed.
Call :func:`~deployer.host.base.HostContext.cd` on the
:class:`~deployer.host.base.HostContext` of every host.
::
with host_container.cd('directory'):
host_container.run('ls')
"""
return nested(* [ h.host_context.cd(*a, **kw) for h in self._all ])
return nested(* [ h.host_context.cd(path, expand=expand) for h in self._all ])

@wraps(HostContext.env)
def env(self, *a, **kw):
def env(self, variable, value, escape=True):
"""
Call 'env' with this parameters on every host.
Sets an environment variable.
This calls :func:`~deployer.host.base.HostContext.env` on the
:class:`~deployer.host.base.HostContext` of every host.
::
with host_container.cd('VAR', 'my-value'):
host_container.run('echo $VAR')
"""
return nested(* [ h.host_context.env(*a, **kw) for h in self._all ])
return nested(* [ h.host_context.env(variable, value, escape=escape) for h in self._all ])

def getcwd(self):
""" Call getcwd() for every host """
""" Calls :func:`~deployer.host.base.Host.getcwd` for every host and return the result as an array. """
return [ h._host.getcwd() for h in self ]

#
Expand All @@ -277,7 +306,8 @@ def getcwd(self):
#
def exists(self, filename, use_sudo=True):
"""
Returns ``True`` when this file exists on the hosts.
Returns an array of boolean values that represent whether this a file
with this name exist for each host.
"""
def on_host(container):
return container._host.exists(filename, use_sudo=use_sudo)
Expand Down Expand Up @@ -311,7 +341,8 @@ def is_64_bit(self): # TODO: deprecate!!!

class HostContainer(HostsContainer):
"""
Similar to hostsContainer, but wraps only around exactly one host.
Similar to :class:`~deployer.host_container.HostsContainer`, but wraps only
around exactly one :class:`~deployer.host.base.Host`.
"""
@property
def _host(self):
Expand All @@ -326,7 +357,7 @@ def slug(self):
return self._host.slug

@wraps(Host.get_file)
def get_file(self, *args,**kwargs):
def get_file(self, *args, **kwargs):
kwargs['sandbox'] = self._sandbox
return self._host.get_file(*args, **kwargs)

Expand Down Expand Up @@ -369,10 +400,15 @@ def __getattr__(self, name):
def expand_path(self, *a, **kw):
return HostsContainer.expand_path(self, *a, **kw)[0]

@wraps(HostsContainer.exists)
def exists(self, *a, **kw):
return HostsContainer.exists(self, *a, **kw)[0]
def exists(self, filename, use_sudo=True):
"""
Returns ``True`` when this file exists on the hosts.
"""
return HostsContainer.exists(self, filename, use_sudo=use_sudo)[0]

@wraps(HostsContainer.has_command)
def has_command(self, *a, **kw):
return HostsContainer.has_command(self, *a, **kw)[0]
def has_command(self, command, use_sudo=False):
"""
Test whether this command can be found in the bash shell, by executing
a ``which`` Returns ``True`` when the command exists.
"""
return HostsContainer.has_command(self, command, use_sudo=use_sudo)[0]
15 changes: 14 additions & 1 deletion deployer/node/base.py
Expand Up @@ -205,7 +205,9 @@ def default_from_node(cls, node):
Create a default environment for this node to run.
It will be attached to stdin/stdout and commands will be logged to
stdout. The is the most obvious default.
stdout. The is the most obvious default to create an ``Env`` instance.
:param node: :class:`~deployer.node.base.Node` instance
"""
from deployer.pseudo_terminal import Pty
from deployer.loggers import LoggerInterface
Expand Down Expand Up @@ -647,7 +649,10 @@ def __new__(cls, parent=None):

@_internal
def __init__(self, parent=None):
#: Reference to the parent :class:`~deployer.node.base.Node`.
#: (This is always assigned in the constructor. You should never override it.)
self.parent = parent

if self._node_type in (NodeTypes.SIMPLE_ARRAY, NodeTypes.SIMPLE_ONE) and not parent:
raise Exception('Cannot initialize a node of type %s without a parent' % self._node_type)

Expand Down Expand Up @@ -801,12 +806,20 @@ class ParallelNode(Node):
Multiple hosts can be given for this role, but all of them will be isolated,
during execution. This allows parallel executing of functions on each 'cell'.
If you call a method on a ``ParallelNode``, it will be called one for every
host, which can be accessed through the ``host`` property.
:note: This was called `SimpleNode` before.
"""
__metaclass__ = ParallelNodeBase
_node_type = NodeTypes.SIMPLE

def host(self):
"""
This is the proxy to the active host.
:returns: :class:`~deployer.host_container.HostContainer` instance.
"""
if self._node_is_isolated:
host = self.hosts.filter('host')
if len(host) != 1:
Expand Down
12 changes: 6 additions & 6 deletions docs/index.rst
Expand Up @@ -27,12 +27,12 @@ meant to replace anything, it's another tool for your toolbox.
.. _Fabric: http://docs.fabfile.org/
.. _Saltstack: http://saltstack.com

Questions? Just `create a ticket <create-ticket>`_ in Github for now:
Questions? Just `create a ticket`_ in Github for now:

.. _create-ticket: https://github.com/jonathanslenders/python-deployer/issues?state=open
.. _create a ticket: https://github.com/jonathanslenders/python-deployer/issues?state=open

- Read the tutorials: :ref:`Hello world <getting-started>` and :ref:`Django
tutorial <django-tutorial>`
- Read the tutorials: :ref:`Hello world <hello-world>` and :ref:`Deploying an
application <django-tutorial>`
- Find the source code at `github`_.

.. _github: https://github.com/jonathanslenders/python-deployer
Expand All @@ -45,8 +45,8 @@ Table of contents
.. toctree::
:maxdepth: 3

pages/getting_started
examples/django-deployment
pages/hello_world
pages/django-deployment
pages/architecture_of_roles_and_nodes
pages/interactive_shell

Expand Down
Expand Up @@ -123,9 +123,9 @@ the repository. You can add the ``install_git``, ``git_clone`` and
with self.host.cd(self.project_directory, expand=True):
self.host.run("git checkout '%s'" % esc1(commit))

Probably obvious, we have a clone and checkout function that are meant to move
to a certain directory on the server and run a shell command in there. Some
points worth noting:
Probably obvious, we have a clone and checkout function that are meant to go
to a certain directory on the server and run a shell command in there through
:func:`~deployer.host.base.Host.run`. Some points worth noting:

- ``expand=True``: this means that we should do tilde-expension. You want the
tilde to be replaced with the home directory. If you have an absolute path,
Expand Down Expand Up @@ -254,6 +254,7 @@ Anyway, suppose that you have a configuration that you want to upload to
"""

class DjangoDeployment(Node):
...
def upload_django_settings(self):
""" Upload the content of the variable 'local_settings' in the
local_settings.py file. """
Expand Down Expand Up @@ -858,4 +859,4 @@ would overwrite it with.
Also, learn about :ref:`query expressions <query-expressions>` and the
``parent`` variable which are very powerful.
:attr:`~deployer.node.base.Node.parent` variable which are very powerful.
@@ -1,4 +1,4 @@
.. _getting-started:
.. _hello-world:

Tutorial: Hello world
=====================
Expand Down
19 changes: 11 additions & 8 deletions docs/pages/host_container.rst
@@ -1,13 +1,14 @@
host_container
==============

Access to hosts from within a ``Node`` class happens through a
``HostsContainer`` proxy. This container object has also methods for reducing
the amount of hosts on which commands are executed, by filtering according to
conditions.
Access to hosts from within a :class:`~deployer.node.base.Node` class happens
through a :class:`~deployer.host_container.HostsContainer` proxy. This
container object has also methods for reducing the amount of hosts on which
commands are executed, by filtering according to conditions.

The ``hosts`` property of a node instance returns such a ``HostsContainer``
object.
The :attr:`~deployer.node.base.Env.hosts` property of
:class:`~deployer.node.base.Env` wrapper around a node instance returns such a
:class:`~deployer.host_container.HostsContainer` object.

::

Expand All @@ -17,12 +18,14 @@ object.
caching_servers = Host3

def do_something(self):
# self.hosts here, is a HostsContainer instance.
# ``self.hosts`` here is a HostsContainer instance.
self.hosts.filter('caching_servers').run('echo hello')

Reference
---------

.. automodule:: deployer.host_container
.. autoclass:: deployer.host_container.HostsContainer
:members:

.. autoclass:: deployer.host_container.HostContainer
:members:
11 changes: 7 additions & 4 deletions docs/pages/interactive_shell.rst
Expand Up @@ -5,8 +5,9 @@ The interactive shell
=====================

It's very easy to create an interactive command line shell from a node tree.
Suppose that you have a `deployer.node.Node` called ``MyRootNode``, then you
can create a shell by making an executable file like this:
Suppose that you have a :class:`~deployer.node.base.Node` called
``MyRootNode``, then you can create a shell by making an executable file like
this:

::

Expand Down Expand Up @@ -51,8 +52,10 @@ If you save this as ``client.py`` and call it by typing ``python ./client.py
--version : Show version information.

There are several options to start such a shell. It can be multi or single
threaded, or you can run it as a telnet-server. Normally, you just type the
following to get the interactive prompt:
threaded, or you can run it as a telnet-server. Assuming you made the file also
executable using ``chmod +x client.py``, you just type the following to get the
interactive prompt:


::

Expand Down

0 comments on commit 98c7625

Please sign in to comment.