batou

automating web application deployments

Release v1.13.0.

batou is a BSD licensed utility, written in Python, to configure development and production environments for web applications.

Deployments are hard and complicated. Tools like Docker, Puppet, chef, and others exist that try to solve this problem once and for all. However, they usually need you to change your workflow or toolchain massively while still missing important steps.

batou makes deployments more bearable without requiring developers to change their applications. It provides a “one command” approach that should never need additional wrapper scripts.

As a developer all you ever have to run after cloning or updating your project is:

$ hg clone https://bitbucket.org/example/myproject
$ cd myproject
$ ./batou deploy dev

To run a production deployment all you should ever have to run is:

$ cd my-project
$ git pull
$ ./batou deploy prod

Writing a deployment with batou is a two step process:

Step 1: Model your application’s configuration

With our component model you write a configuration specification in Python based on a simple API. Components make configuration convergent and idempotent and using Python lets you perform any computation you need. The component model is recursive, so you can refactor complicated components into simpler ones without breaking your setup.

Here is an example application model that installs a Python package into a VirtualEnv and asks Supervisor to run it:

from batou.component import Component
from batou.lib.python import VirtualEnv, Package
from batou.lib.supervisor import Program

class MyApp(Component):

  def configure(self):
    venv = VirtualEnv('2.7')
    self += venv
    venv += Package('myapp')
    self += Program('myapp', command='bin/myapp')

Step 2: Fit your model to your environments

Your model from step 1 is abstract: it does not mention the names of the servers you deploy to.

By describing an environment you tell batou how your abstract model should actually be applied: on your local development machine, to a vagrant setup, or on servers on the network.

Here’s an environment specification that sets up an application on multiple hosts and provides an override for the publicly visible address.

[environment]
host_domain = fcio.net

[host:host01]
components = nginx, haproxy, varnish

[host:host02]
components = myapp

[host:host03]
components = myapp

[host:host04]
components = postgresql

[component:nginx]
server_name = staging.example.com

Features

  • Run the same command to deploy locally, to Vagrant, or to remote clusters.
  • Use different versions of batou in different projects. Ensure everybody uses the same version.
  • Check before deploying whether your configuration is internally consistent and consistent with what has been deployed before.
  • Convergent, idempotent components are fast to deploy.
  • Resume partial deployments where they were aborted.
  • Store database passwords, SSH keys, SSL certificates or other secret data with on the-fly decryption. Manage access to secrets per environment and user.
  • Use Jinja2 templates to easily create dynamic configuration.
  • Dynamically connect services during deployments and track their dependencies.
  • Few run-time requirements on your servers: only Python 2.7 and SSH are needed.
  • Use pre-defined components to manage files, python environments, supervisor, cronjobs, and more.
  • Writing your own components is easy and you can use additional Python package dependencies.

User guide

This part of the documentation, begins with some background information about Requests, then focuses on step-by-step instructions for getting the most out of batou.

Introduction

Philosophy

batou was developed with a number of ideas in mind:

  • Deploying should always be just a single command.
  • Python is the language batou uses. This is not to be confused with the Python that your application might use.
  • We expect little from the remote environments regarding software dependencies: Python 2.7, OpenSSH, and rsync, Mercurial or git should be sufficient.
  • batou does not become an active component during your application’s runtime. batou automates what a sysadmin would do – in a structured manner.
  • It should be easy to switch between the declarative part of the model and the imperative implementation.
  • Deployment code that become too complicated should be easy to simplify by breaking it up into smaller pieces.
  • batou is not intended to perform provisioning or system configuration tasks. We expect the heavy lifting (preparing the target OS) to already be done.
  • batou should be working with your existing applications without too many hassles.
  • No silver bullets: we want to make things simple for you but we do not insulate you in ways that make you blind

Name

The name “batou” is taken from the animated movie “Ghost in the Shell”.

Kudos

batou is built on the shoulders of giants. We’re extremely happy to be part of an active open source community:

  • Guido and the Python core developers – we could not have built this without such an awesome language.
  • Jim Fulton (@j1mfulton) and zc.buildout – we love using buildout to create Python application environments, but we started having our own ideas at some point.
  • Kenneth Reitz (@kennethreitz) and the Requests team – who built the awesome requests library. He and his team have brilliant documentation. When it came to finally document batou we used their works – quite literally and generously! Everything that is awesome about our documentation stems from them. Everything that sucks was caused by us (if you want to help us improve – you’re more than welcome!).
  • Jeff Forcier (@bitprophet) of Fabric and Paramiko fame – we’ve made huge advances when we switched from bash scripts to Fabric and kept refactoring until our Fabfile slowly turned into batou. We’ve used Paramiko before switching to execnet which gave us a nice way to slowly grow out of Fabric.
  • Holger Krekel (@hpk42) who gave us py.tests and execnet – without those tools we would not have such a nice test suite and execnet is brilliant for lightweight remote Python processing.

Installation

batou is installed in each of your projects to ensure that every project is run with the correct version of batou and possibly other dependencies.

Starting a new batou project

A new project is started by placing the batou master command into the project and adding that to your repository:

$ mkdir myproject
$ cd myproject
$ git init
$ curl -L https://batou.readthedocs.io/en/latest/batou -o batou
$ chmod +x batou
$ ./batou
Preparing virtualenv in .batou ...
Pre-installing batou - this can take a while...
Installing Jinja2==2.7.3
Installing requests==2.6.0
Installing setuptools==18.3.1
Installing execnet==1.3.0
Installing py==1.4.26
usage: batou [-h] [-d] [-F] {deploy,remote,secrets,init,update} ...
batou: error: too few arguments
$ git add batou
$ git commit -m "Start a batou project."

Local

To run the master command on your machine you will need to have the following dependencies installed:

  • Python 2.7
  • virtualenv
  • OpenSSH
  • GPG (optional, if you want to use encrypted secrets support)
  • Mercurial, git, or rsync (you only need to have the one installed you actually use to transfer your repository)

Remote

To deploy on a remote server, that server needs to have installed:

  • Python 2.7
  • virtualenv
  • OpenSSH
  • Mercurial, git or rsync (you only need to have the one installed you actually use to transfer your repository)

Supported Platforms

batou is being tested to run on Linux and Mac OS X.

We do not support deploying from or to non-UNIX targets.

batou is written in Python and requires to be run with Python 2.7.

Optional requirements

Depending on the actual components you are going to install, you may need to have those packages installed on your remote machines:

Distribution-specific installation instructions

Install batou’s requirements on Debian / Ubuntu / Mint

Depending on you specific distribution, software packages either need to be installed using apt-get or aptitude. apt-get is more specific to Ubuntu and its derivates, whereas aptitude is typically used on Debian. In our examples below, we will stick to apt-get.

Python 2.7

In many distributions, Python 2.7 should ship as the default system Python and should therefore already be installed. If not, you can install it by executing:

sudo apt-get install python2.7-dev

Note

Although installing using sudo apt-get install python2.7 would be sufficient, we choose to python2.7-dev because having the additional development packages installed does not hurt and may help in situations that require functionality that is not included in the base package.

virtualenv

virtualenv creates isolated environments for Python, where you can install and upgrade libraries isolated from the system libraries and other environments on the system. You can install virtualenv by executing:

sudo apt-get install python-virtualenv
Mercurial

Mercurial is a distributed source control management tool. You can install Mercurial by executing:

sudo apt-get install mercurial
SSH client

The OpenSSH client should ship with nearly any distribution and should already be installed. If not, you can install it by executing:

sudo apt-get install openssh-client
Git

Git is a distributed revision control and source code management system. You can install Git by executing:

sudo apt-get install git

In some cases you may want more functionality than the basic git package offers. To get a list of software packages related to git, execute:

sudo apt-cache search . | grep ^git

You can then install the respective package with apt-get like we did for git.

make

GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program’s source files. It is typically available on nearly any Linux distribution.

Since it is heavily involved in self-compiling software, it is advisable to additionally install a useful set of packages that helps compiling software:

sudo apt-get install build-essential
NRPE

NRPE is an add-on for Nagios that allows you to execute plugins and commands on remote Linux/Unix machines. NRPE can be installed by executing:

sudo apt-get install nagios-nrpe-server
rsync

rsync is a file synchronization and file transfer program for Unix-like systems. You can install rsync by executing:

sudo apt-get install rsync
Subversion

Subversion is a software versioning and revision control system. To install it, execute:

sudo apt-get install subversion

Note

Additionally installing the package subversion-tools may be helpful when you need more functionality and helper tools for Subversion.

UnZip

UnZip is an extraction utility for archives compressed in .zip format. You can install it by executing:

sudo apt-get install unzip
Tar

GNU Tar provides the ability to create tar archives, as well as various other kinds of manipulation. It should already ship with nearly any Linux distribution. If not, you can install it by executing:

sudo apt-get install tar
Install batou’s requirements on Fedora / openSUSE / RHEL / CentOS

On rpm-based Linux distributions software packages are usually installed using yum.

Python 2.7

In many distributions, Python 2.7 should ship as the default system Python and should therefore already be installed. If not, you can install it by executing:

sudo yum install python-devel

Note

Although installing using sudo yum install python would be sufficient, we choose to python-devel because having the additional development packages installed does not hurt and may help in situations that require functionality that is not included in the base package.

virtualenv

virtualenv creates isolated environments for Python, where you can install and upgrade libraries isolated from the system libraries and other environments on the system. You can install virtualenv by executing:

sudo yum install python-virtualenv
Mercurial

Mercurial is a distributed source control management tool. You can install Mercurial by executing:

sudo yum install mercurial
SSH client

The OpenSSH client should ship with nearly any distribution and should already be installed. If not, you can install it by executing:

sudo yum install openssh-clients
Git

Git is a distributed revision control and source code management system. You can install Git by executing:

sudo yum install git

In some cases you may want more functionality than the basic git package offers. To get a list of software packages related to git, execute:

sudo yum search git | grep ^git

You can then install the respective package with yum like we did for git.

make

GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program’s source files. It is typically available on nearly any Linux distribution.

Since it is heavily involved in self-compiling software, it is advisable to additionally install a useful set of packages that helps compiling software:

sudo yum groupinstall "Development Tools"

Alternatively, you can install the most basic tools for compiling software by executing:

sudo yum install gcc gcc-c++ kernel-devel
NRPE

NRPE is an add-on for Nagios that allows you to execute plugins and commands on remote Linux/Unix machines. NRPE can be installed by executing:

sudo yum install nrpe
rsync

rsync is a file synchronization and file transfer program for Unix-like systems. You can install rsync by executing:

sudo yum install rsync
Subversion

Subversion is a software versioning and revision control system. To install it, execute:

sudo yum install subversion

Note

Additionally installing the package subversion-tools may be helpful when you need more functionality and helper tools for Subversion.

UnZip

UnZip is an extraction utility for archives compressed in .zip format. You can install it by executing:

sudo yum install unzip
Tar

GNU Tar provides the ability to create tar archives, as well as various other kinds of manipulation. It should already ship with nearly any Linux distribution. If not, you can install it by executing:

sudo yum install tar

Quickstart

Do you want to get started? We’ll go through the steps of developing a project with batou. The steps are built on top of each other, so if you have trouble with a specific step, it might help to review what happened earlier.

Create a new project

Deployments with batou are placed in a new directory. For this tutorial we will assume that you’re using git as your version control system. Feel feel free to follow along using Mercurial – batou can handle both.

$ mkdir myproject
$ cd myproject
$ curl -L https://batou.readthedocs.io/en/latest/batou -o batou
$ chmod +x batou
$ git init
$ git add batou
$ git commit
$ ./batou
Preparing virtualenv in .batou ...
Pre-installing batou - this can take a while...
Installing Jinja2==2.7.3
Installing requests==2.6.0
Installing setuptools==18.3.1
Installing execnet==1.3.0
Installing py==1.4.26
usage: batou [-h] [-d] [-F] {deploy,remote,secrets,init,update} ...
batou: error: too few arguments

The project is now initialized and batou is ready to be used.

Writing a component configuration

Once you bootstrapped your batou project you start modelling your configuration. This is done by creating a directory in the components directory and a component.py file in there. You can use those sub- directories to group together things that belong to each component:

$ cd myproject
$ mkdir -p components/myapp

In components/myapp/component.py put the following to manage a very simple application:

components/myapp/component.py (v1)
from batou.component import Component
from batou.lib.file import File


class Tick(Component):

    def configure(self):
        self += File(
            'tick.sh',
            mode=0755,
            content="""\
#!/bin/bash
while true; do
  date
  sleep 1
done
""")

The component has a configure method that is used to build a model of your configuration as a tree of components. By using the syntax self += File(...) you add a File component as a sub-component to your Tick component. Components can thus recursively combine configurations into larger, more complex setups.

The order of sub-components is given by the order they are added to their parent.

Local environments

Now, to deploy this “application” we can specify a local environment to deploy directly on the machine you are working on:

$ mkdir environments

Put the following in environments/local.cfg to specify a local configuration that will deploy the “Tick” component:

environments/local.cfg (v1)
[environment]
connect_method = local

[hosts]
localhost = tick

Your project now looks like this:

$ tree
.
├── batou
├── components
│   └── myapp
│       └── component.py
└── environments
    └── local.cfg

You can now deploy this environment:

$ ./batou deploy local
============================== Preparing ===============================
main: Loading environment `local`...
main: Verifying repository ...
main: Loading secrets ...
======================= Configuring first host =========================
localhost: Connecting via local (1/1)
===================== Connecting remaining hosts =======================
============================== Deploying ===============================
localhost: Deploying component tick ...
     Tick > File(work/tick/tick.sh) > Presence(work/tick/tick.sh)
     Tick > File(work/tick/tick.sh) > Mode(work/tick/tick.sh)
     Tick > File(work/tick/tick.sh) > Content(work/tick/tick.sh)
========================= DEPLOYMENT FINISHED ==========================

When deploying, batou creates a working directory for each component. Your project directory now looks like this:

$ tree
.
├── batou
├── components
│   └── myapp
│       └── component.py
├── environments
│   └── local.cfg
└── work
    └── tick
        └── tick.sh

The application has been copied over to the work directory and the mode has been set. We can now use it:

$ ./work/tick/tick.sh
Thu Jan 28 21:47:58 CET 2016
Thu Jan 28 21:47:59 CET 2016
Thu Jan 28 21:48:00 CET 2016
Thu Jan 28 21:48:01 CET 2016
Thu Jan 28 21:48:02 CET 2016
Thu Jan 28 21:48:03 CET 2016
^C

When running the deployment again, you see that batou knows what has been deployed and that no action is necessary:

$ ./batou deploy local
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
============================== Preparing ===============================
main: Loading environment `local`...
main: Verifying repository ...
main: Loading secrets ...
======================= Configuring first host =========================
localhost: Connecting via local (1/1)
===================== Connecting remaining hosts =======================
============================== Deploying ===============================
localhost: Deploying component tick ...
========================= DEPLOYMENT FINISHED ==========================

Note

Things in the work directory are generated or data from your application – you should thus add the work directory to your version control systems’ ignore file.

Vagrant environments

If you would like to deploy a more complex application, that may involve a webserver, databases, and other auxiliary services, you may prefer to deploy into a virtual machine, instead of deploying those to your local work environment.

For this, batou supports Vagrant. Once you have Vagrant (and VirtualBox) installed, place a Vagrantfile directory in your batou project:

Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "flyingcircus/nixos-15.09-x86_64"
  config.vm.box_version = ">= 1.2"

  config.vm.network "private_network", ip: "192.168.50.4"
  config.vm.hostname = "default"
end

Now, we add a second environment that uses Vagrant to connect and rsync to ensure that our batou project gets synced. The user we want to deploy to in a vagrant box is vagrant and we specify that as the service user. The machine in our Vagrant file is “default”, so we use that as the hostname:

environments/vagrant.cfg
[environment]
connect_method = vagrant
update_method = rsync
service_user = vagrant

[hosts]
default = tick

The deployment is invoked similar to the local deployment. Getting the vagrant machine up and running may take a while, though:

$ ./batou deploy vagrant
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `vagrant`...
main: Verifying repository ...
     You are using rsync. This is a non-verifying repository --
     continuing on your own risk!
main: Loading secrets ...
===================== Configuring first host =====================
vagrant: Ensuring machines are up ...
default: Connecting via vagrant (1/1)
=================== Connecting remaining hosts ===================
=========================== Deploying ============================
default: Deploying component tick ...
     Tick > File(work/tick/tick.sh) > Presence(work/tick/tick.sh)
     Tick > File(work/tick/tick.sh) > Mode(work/tick/tick.sh)
     Tick > File(work/tick/tick.sh) > Content(work/tick/tick.sh)
====================== DEPLOYMENT FINISHED =======================

Now, the tick component has been deployed on the virtual machine. We can connect there and see that the same structure has been deployed as previously for our local environment. batou places deployments in the service user’s home directory in a directory named deployment by default:

$ vagrant ssh
[vagrant@nixos:~]$ tree
.
└── deployment
    ├── batou
    ├── components
    │   └── myapp
    │       └── component.py
    ├── environments
    │   ├── local.cfg
    │   └── vagrant.cfg
    ├── Vagrantfile
    └── work
        └── tick
            └── tick.sh
[vagrant@nixos:~]$ ./deployment/work/tick/tick.sh
Thu Jan 28 21:02:31 UTC 2016
Thu Jan 28 21:02:32 UTC 2016
Thu Jan 28 21:02:33 UTC 2016
^C

Similarly, you can deploy into VMs that were set up by the test kitchen integration testing tool:

.kitchen.yml
---
driver:
  name: vagrant

platforms:
  - name: ubuntu-16.04

suites:
  - name: tick
environments/kitchen.cfg
[environment]
connect_method = kitchen
update_method = rsync
service_user = vagrant

[hosts]
tick-ubuntu-1604 = tick
$ ./batou deploy kitchen

Remote environments

To deploy your application into a production environment you will typically use SSH to log in to the remote servers. This works similar to Vagrant environments. To try this out, you will have to replace the host name you see here, with a host that you have access to.

Note

Make sure that the few but important installation requirements requirements for remote hosts are satisfied!

Let’s add a third environment that uses SSH to connect and rsync to ensure that our batou project gets synced. We do not specify the user to deploy to, which means batou will use whatever your SSH configuration is set up to use. To save some typing (and for some other features) we specify a domain name that should be appended to all hosts.

Here’s the full environment configuration:

environments/production.cfg (v2)
[environment]
connect_method = ssh
update_method = rsync
host_domain = fcio.net

[hosts]
test01 = tick

Now, to deploy to the remote host:

$ ./batou deploy production
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `production`...
main: Verifying repository ...
     You are using rsync. This is a non-verifying repository --
     continuing on your own risk!
main: Loading secrets ...
===================== Configuring first host =====================
test01: Connecting via ssh (1/1)
=================== Connecting remaining hosts ===================
=========================== Deploying ============================
test01.gocept.net: Deploying component tick ...
     Tick > File(work/tick/tick.sh) > Presence(work/tick/tick.sh)
     Tick > File(work/tick/tick.sh) > Mode(work/tick/tick.sh)
     Tick > File(work/tick/tick.sh) > Content(work/tick/tick.sh)
====================== DEPLOYMENT FINISHED =======================

Overriding configuration per environment

Every environment is currently deploying the same configuration of our application. It often is necessary to customize applications based on the environment: either your setup is larger or smaller, or you are using a different web address to access it, or …

To make a component configurable, we add an attribute to the component class:

components/myapp/component.py (v2)
from batou.component import Component, Attribute
from batou.lib.file import File


class Tick(Component):

    sleep = Attribute(int, 1)

    def configure(self):
        self += File(
            'tick.sh',
            mode=0755,
            content="""\
#!/bin/bash
while true; do
  date
  sleep {}
done
""".format(self.sleep))

Attributes are specified with a conversion function or type, to help batou convert them from strings. The second argument given is the default that will be used when attribute is not specified explicitly otherwise. The attribute can then be accessed as usual during the configure method and include this in our application configuration.

To adjust the application for the development environment, we add a new section [component:tick] to the configuration:

environments/local.cfg (v2)
[environment]
connect_method = local

[hosts]
localhost = tick

[component:tick]
sleep = 10

Now, let’s deploy this:

$ ./batou deploy local
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `local`...
main: Verifying repository ...
main: Loading secrets ...
===================== Configuring first host =====================
localhost: Connecting via local (1/1)
=================== Connecting remaining hosts ===================
=========================== Deploying ============================
localhost: Deploying component tick ...
     Tick > File(work/tick/tick.sh) > Content(work/tick/tick.sh)
====================== DEPLOYMENT FINISHED =======================

$ cat work/tick/tick.sh
#!/bin/bash
while true; do
  date
  sleep 10
done

$ ./work/tick/tick.sh
Fri Jan 29 09:44:18 CET 2016
Fri Jan 29 09:44:28 CET 2016
^C

Now, our model specific the default configuration and environment-specific overrides allow us to document variations between environments easily.

Templating from files

Currently our application configuration has been written directly in Python code. This quickly becomes unwieldy. File components can be used to pull their content from files by specifying the source parameter. The filename is relative to the directory that the component.py is placed in:

components/myapp/component.py (v3)
from batou.component import Component, Attribute
from batou.lib.file import File


class Tick(Component):

    sleep = Attribute(int, 1)

    def configure(self):
        self += File(
            'tick.sh',
            mode=0755,
            source='tick.sh')

Also, templating with Python format strings is very limited. batou thus includes Jinja2 templating, which is enabled by default for all content (independent of whether you specify the content inline or in a separate file).

components/myapp/tick.sh (v1)
#!/bin/bash
while true; do
  date
  sleep {{component.sleep}}
done

As a simplification, you can leave out the source parameter if the filename of your template is identical to the name you want it to be in the work directory:

components/myapp/component.py (v4)
from batou.component import Component, Attribute
from batou.lib.file import File


class Tick(Component):

    sleep = Attribute(int, 1)

    def configure(self):
        self += File('tick.sh', mode=0755)

To disable templating and encoding handling, you can use the BinaryFile component:

from batou.component import Component
from batou.lib.file import BinaryFile

class Example(Component):

    def configure(self):
        self += BinaryFile('something.zip')

Storing secrets as encrypted overrides

To deploy services we need to move secret data, like database passwords, third party API tokens, SSL certificates, SSH keys, etc. to the target environment. Simply adding those to your deployment scripts has a huge drawback:

  • it’s unsafe to pass the scripts to a third party like Github
  • you can’t let others review your code without accidentally revealing those
  • you can’t manage which of your colleagues can access development, staging, or production secrets.

batou has a built-in method to store secrets in a secure and flexible fashion: secrets are an encrypted version of the overrides we have already used: they are stored in one file per environment, in the same format as the environment configuration, and they control which of your users have access to them.

To start managing a secret, first add a new override attribute database_password to your component:

components/myapp/component.py (v5)
from batou.component import Component, Attribute
from batou.lib.file import File


class Tick(Component):

    database_password = Attribute(str, None)
    sleep = Attribute(int, 1)

    def configure(self):
        self += File('tick.sh', mode=0755)
components/myapp/tick.sh (v2)
#!/bin/bash
while true; do
  date
  echo "The database password is {{component.database_password}}"
  sleep {{component.sleep}}
done

Now, to edit the secret, batou provides a set of commands. Let’s edit the secrets file for our production environment:

$ ./batou secrets edit production

This opens up your preferred editor and provides you with a template file:

secrets/production.cfg (decrypted) (v1)
[batou]
members =

The members option is used to control who the file will be encrypted for – and thus who can decrypt it in the future. You can specify any ID that GnuPG will accept as a key ID, which usually means you use your email address associated with your key.

To get started with GPG, check the GnuPG HOWTOs.

In addition to your own key, you will have to add the IDs of any of your colleagues that should be able to access this file.

Adding the database password works similar to the environment overrides. Our final file then looks like this:

secrets/production.cfg (decrypted) (v2)
[batou]
members = bob@example.com, alice@example.com

[component:tick]
database_password = AfMhV3EDznGbNnzVdxE8

To finish, save the file and exit your editor. batou will be careful not to leave any unencrypted copy of the file around so you do not accidentally check in the unencrypted version.

The encrypted version of the file is now stored in secrets/production.cfg and might look like this:

secrets/production.cfg (encrypted, hexdump -C)
00000000  85 02 0c 03 e4 fa c7 12  8f d9 8a 97 01 0f fe 32  |...............2|
00000010  d0 f7 f2 51 77 b5 89 9c  cb 3f 78 15 94 20 d9 dd  |...Qw....?x.. ..|
00000020  7d c3 52 93 e0 cc c5 09  c8 01 bc 32 11 fc 0c d0  |}.R........2....|
00000030  04 13 09 47 ab 2b e2 f0  12 51 fe 26 23 84 5d d6  |...G.+...Q.&#.].|
00000040  19 28 8f 6b f1 4b dc 39  cb 95 dd 31 52 09 b8 f0  |.(.k.K.9...1R...|
00000050  c8 99 0a 86 d3 f1 28 e6  6a 41 39 45 d3 ae 9a 01  |......(.jA9E....|
00000060  07 22 7b ce 7d b4 7c d5  22 16 11 8a 1d a5 9f cb  |."{.}.|.".......|
00000070  96 50 1e 30 16 ec 45 44  10 c0 73 40 e0 97 23 bf  |.P.0..ED..s@..#.|
00000080  ac b9 ea 46 df c4 67 a4  83 ae 4a 24 e4 6e 13 f9  |...F..g...J$.n..|
00000090  ad 9d 87 07 59 d4 46 0b  53 80 50 c1 e0 1d 79 be  |....Y.F.S.P...y.|
000000a0  53 e5 25 15 de 54 6d 65  be 37 35 81 4d 34 55 35  |S.%..Tme.75.M4U5|
000000b0  3c 08 13 db cf 0e e8 f5  9d fb f2 09 ca 22 f2 97  |<............"..|
000000c0  8d bb bb 7c 6e e4 b9 7a  92 eb 75 08 43 15 f9 07  |...|n..z..u.C...|
000000d0  40 24 a8 8e a1 4d 53 6f  7b fe df 07 d8 89 29 ad  |@$...MSo{.....).|
000000e0  a2 df 0d 40 d4 7e 25 b4  b7 cd e9 e8 71 de ff df  |...@.~%.....q...|
000000f0  b8 0d 4f bd 83 63 c0 02  88 d2 79 48 f6 05 76 66  |..O..c....yH..vf|
00000100  76 b3 44 34 36 74 16 b6  1d f1 c0 38 9a ac 33 e5  |v.D46t.....8..3.|
00000110  99 1c 69 10 45 72 28 8f  f8 b5 e1 71 71 fb 8e 8a  |..i.Er(....qq...|
00000120  e7 13 a4 0d dc 1e 42 f1  82 c6 83 cf a0 d8 ef e9  |......B.........|
00000130  f8 33 0c 8c 10 f8 5a 56  69 47 3f d4 65 57 10 1d  |.3....ZViG?.eW..|
00000140  cb 19 4d 51 68 d5 68 fe  82 c1 4f 7b e9 b9 23 12  |..MQh.h...O{..#.|
00000150  04 41 a0 88 14 85 27 23  86 92 77 62 7a 20 80 74  |.A....'#..wbz .t|
00000160  14 9e c7 e8 82 79 1c 10  04 f5 f4 67 94 b7 3e 8e  |.....y.....g..>.|
00000170  30 95 57 ab 0e 20 fb 4a  1f 10 c2 60 38 63 78 41  |0.W.. .J...`8cxA|
00000180  38 32 0d 48 35 3e b2 d1  19 9e 37 02 26 6f 11 c3  |82.H5>....7.&o..|
00000190  83 8f dd fe 11 12 8b c8  43 96 dd 49 b7 db f1 b7  |........C..I....|
000001a0  e9 09 44 8b 23 0d 71 3e  cc f8 a7 d2 e2 79 65 94  |..D.#.q>.....ye.|
000001b0  53 36 c6 43 19 df 7d 69  33 cb c0 ab 4c c3 db 7f  |S6.C..}i3...L...|
000001c0  b3 8f a9 35 a6 7d fb 94  6f df 04 37 88 44 a4 df  |...5.}..o..7.D..|
000001d0  66 39 2d 17 f2 a9 57 60  2f 11 10 ff 43 03 58 4c  |f9-...W`/...C.XL|
000001e0  5f bd f1 17 0c e8 c6 60  69 fe 6e 86 65 fa 12 2f  |_......`i.n.e../|
000001f0  12 91 80 9e 5d f7 da df  c0 6c 2a 90 40 94 f0 07  |....]....l*.@...|
00000200  3e a2 d6 07 83 71 28 e0  d3 26 76 51 d6 23 49 d2  |>....q(..&vQ.#I.|
00000210  95 01 24 3c 40 18 2c 05  65 4b c4 4a 86 3f 67 db  |..$<@.,.eK.J.?g.|
00000220  ac 5c e8 3e 49 90 5f f9  66 e5 0f 35 ad e8 99 57  |.\.>I._.f..5...W|
00000230  13 b5 4a b4 59 38 de a0  1c 89 67 e3 2f 3e b1 d8  |..J.Y8....g./>..|
00000240  0b 37 b4 d6 58 ee bf 47  f6 53 64 ed 70 ba 37 f5  |.7..X..G.Sd.p.7.|
00000250  be 56 e0 69 52 18 0e 04  ff e4 2d 05 43 c5 a5 4f  |.V.iR.....-.C..O|
00000260  04 a5 4e a1 d7 c4 5f 65  02 02 5c 29 fe 2c 34 c5  |..N..._e..\).,4.|
00000270  1b 65 4c 88 85 8c 6f ce  15 4e e7 43 2d db fb eb  |.eL...o..N.C-...|
00000280  28 b6 b2 2b b1 cc b2 04  1b bd 17 a5 89 5c fd 3f  |(..+.........\.?|
00000290  c7 bf 60 df 58 8d 41 35  2a 14 9f e5 99 83 a1 97  |..`.X.A5*.......|
000002a0  84 5a 5d ae 83 0e                                 |.Z]...|
000002a6

Now, deploying this will cause GPG to be invoked on your local machine (there is no need to install GPG on the remote hosts) and transfer the decrypted secrets securely through the SSH connection:

$ ./batou deploy production
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `production`...
main: Verifying repository ...
     You are using rsync. This is a non-verifying repository --
     continuing on your own risk!
main: Loading secrets ...

You need a passphrase to unlock the secret key for
user: "Bob Sample <bob@example.com>"
4096-bit RSA key, ID 4DE34B34, created 2013-10-05 (main key ID 4DE34B34)

===================== Configuring first host =====================
test01: Connecting via ssh (1/1)
=================== Connecting remaining hosts ===================
=========================== Deploying ============================
test01.gocept.net: Deploying component tick ...
     Tick > File(work/tick/tick.sh) > Content(work/tick/tick.sh)
====================== DEPLOYMENT FINISHED =======================

$ ssh test01.fcio.net
$ cat ./deployment/work/tick/tick.sh
while true; do
  date
  echo "The database password is AfMhV3EDznGbNnzVdxE8"
  sleep 1
done

If you deal with a growing number of environments and users, you can use the commands batou secrets add and batou secrets remove to manage access for users to multiple or all environments quickly.

Using version control to ensure consistent deployments

Working in a team means that some of your colleagues may be deploying code independent of you. Deploying when a colleague may have deployed last Friday and forgot to push his code can lead to bad things happening …

batou supports integrating with Git or Mercurial to verify the repository integrity on the target systems. This consists of multiple steps:

  1. Ensuring you do not have uncommitted changes and no unpushed commits in your repository.
  2. Shipping changes to the remote servers (either via pulling from a central server or using an export/import mechanism if you server has no access to your central repository).
  3. Switching to an environment-specific branch.
  4. Ensuring the local working copy and the remote working copy are the same.

To leverage those features in batou, you have to select an update method in your environment that is not rsync. batou supports git-pull, git- bundle, hg-pull and hg-bundle.

Lets use git-bundle for this example:

environments/production.cfg (v1)
[environment]
connect_method = ssh
update_method = git-bundle
branch = production
host_domain = fcio.net

[hosts]
test01 = tick

To deploy to production we now end up with the following workflow:

$ git checkout -tb production
$ git merge master
$ ./batou deploy production
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `production`...
main: Verifying repository ...
main: Loading secrets ...
===================== Configuring first host =====================
test01: Connecting via ssh (1/1)
=================== Connecting remaining hosts ===================
=========================== Deploying ============================
test01.gocept.net: Deploying component tick ...
====================== DEPLOYMENT FINISHED =======================

Now, if the remote server would have incompatible changes, batou would inform you and refuse to deploy until you clean up the situation:

$ ./batou deploy remote
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `production`...
main: Verifying repository ...
main: Loading secrets ...
===================== Configuring first host =====================
test01: Connecting via ssh (1/1)
ERROR: LANG=C LC_ALL=C LANGUAGE=C git bundle create /var/folders/
24/4w5jy6r532d0k5mgrrgkt0qm0000gn/T/tmp1ezxQj
36052b45d6cfd7ec7202cf34301fba76c1360c0e..production
     Return code: 1
STDOUT

STDERR
     fatal: Invalid revision range
      36052b45d6cfd7ec7202cf34301fba76c1360c0e..production
     error: rev-list died

======================= DEPLOYMENT FAILED ========================

Note

The error message may be cryptic at times, depending on the error situation of your version control system, but you’ll get the gist to look at the repository situation.

Downloading and building software

A typical action that you encounter when configuring software is to download and compile them. This is also known as the “CMMI” method: configure, make, make install.

batou provides a standard library component that subsumes downloading and building standard packages:

components/zlib/component.py
from batou.component import Component
from batou.lib.cmmi import Build

class Zlib(Component):

  def configure(self):
    self += Build(
      'http://zlib.net/zlib-1.2.8.tar.gz',
      checksum='md5:1142191120b845f4ed8c8c17455420ac')

Deploying this demonstrates how the build component includes sub-components, which you can also use to perform more fine-grained builds:

$ ./batou deploy local
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `local`...
main: Verifying repository ...
main: Loading secrets ...
===================== Configuring first host =====================
localhost: Connecting via local (1/1)
=================== Connecting remaining hosts ===================
=========================== Deploying ============================
localhost: Deploying component zlib ...
   Zlib > Build(zlib-1.2.8.tar.gz) > Extract(zlib-1.2.8.tar.gz) >
      Untar(zlib-1.2.8.tar.gz) > Directory(work/zlib/zlib-1.2.8)
   Zlib > Build(zlib-1.2.8.tar.gz) > Extract(zlib-1.2.8.tar.gz) >
      Untar(zlib-1.2.8.tar.gz)
   Zlib > Build(zlib-1.2.8.tar.gz) >
      Configure(/private/tmp/myproject/work/zlib/zlib-1.2.8)
   Zlib > Build(zlib-1.2.8.tar.gz) >
      Make(/private/tmp/myproject/work/zlib/zlib-1.2.8)
localhost: Deploying component tick ...
====================== DEPLOYMENT FINISHED =======================

Managing Python environments with VirtualEnv and Pip

Installing Python software has some best practices, that involve managing virtual environments, using Pip or zc.buildout. batou provides components that let you manage virtual environments (and automatically update them and keep them in order) for different Python versions.

batou includes pre-defined versions of virtualenv and Pip for the various supported Python versions.

Here is how a component installing Python packages looks like:

components/flask/component.py (v1)
from batou.component import Component
from batou.lib.python import VirtualEnv, Package


class Flask(Component):

    def configure(self):
        venv = VirtualEnv('2.7')
        self += venv

        venv += Package('Flask', version='0.10.1')

Add the “Flask” compoment to your local configuration:

environments/local.cfg (v3)
[environment]
connect_method = local

[hosts]
localhost = tick, flask

[component:tick]
sleep = 10
$ ./batou deploy local
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `local`...
main: Verifying repository ...
main: Loading secrets ...
===================== Configuring first host =====================
localhost: Connecting via local (1/1)
=================== Connecting remaining hosts ===================
=========================== Deploying ============================
localhost: Deploying component tick ...
localhost: Deploying component zlib ...
localhost: Deploying component flask ...
  Flask > VirtualEnv(2.7) > VirtualEnvPy2_7 >
    VirtualEnvDownload(13.1.2) > Download(https://pypi.fcio.net/packages/source/v/virtualenv/virtualenv-13.1.2.tar.gz)
  Flask > VirtualEnv(2.7) > VirtualEnvPy2_7 >
    VirtualEnvDownload(13.1.2) > Extract(virtualenv-13.1.2.tar.gz) >
    Untar(virtualenv-13.1.2.tar.gz)
  Flask > VirtualEnv(2.7) > VirtualEnvPy2_7 > VirtualEnvDownload(13.1.2)
  Flask > VirtualEnv(2.7) > VirtualEnvPy2_7
  Flask > VirtualEnv(2.7) > Package(Flask==0.10.1)
====================== DEPLOYMENT FINISHED =======================

Note

Due to the work directory separation for each component you can easily manage many different virtual environments with different packages and different Python versions.

Managing Python environments with zc.buildout

If you manage your Python environment with zc.buildout, you can automate that easily as well. Place your buildout.cfg next to your component.py and use the following component:

components/flask/component.py (v2)
from batou.component import Component
from batou.lib.python import VirtualEnv, Package


class Flask(Component):

    def configure(self):
        venv = VirtualEnv('2.7')
        self += venv

        venv += Package('Flask', version='0.10.1')
components/flask/buildout.cfg
[buildout]
parts = flask
allow-picked-versions = false
versions = versions

[flask]
recipe = zc.recipe.egg
eggs = Flask

[versions]
zc.recipe.egg = 2.0.3
zc.buildout = 2.5.0
setuptools = 19.6.1
Flask = 0.10.1
itsdangerous = 0.24
Jinja2 = 2.8
Werkzeug = 0.11.3
MarkupSafe = 0.23
$ ./batou deploy local
batou/1.2b1 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `local`...
main: Verifying repository ...
main: Loading secrets ...
===================== Configuring first host =====================
localhost: Connecting via local (1/1)
=================== Connecting remaining hosts ===================
=========================== Deploying ============================
localhost: Deploying component flask ...
   Flask > Buildout > File(work/flask/buildout.cfg) >
      Presence(work/flask/buildout.cfg)
   Flask > Buildout > File(work/flask/buildout.cfg) >
      Content(work/flask/buildout.cfg)
   Flask > Buildout > VirtualEnv(2.7) > VirtualEnvPy2_7
   Flask > Buildout > VirtualEnv(2.7) > Package(setuptools==19.6.1)
   Flask > Buildout > VirtualEnv(2.7) > Package(zc.buildout==2.5.0)
   Flask > Buildout
====================== DEPLOYMENT FINISHED =======================

Registering programs with supervisor

If you’re used to running your programs in supervisor, batou can help you with a pre-made Supervisor component. You can simply enable it by importing it into a component file and registering it with your environment:

components/supervisor/component.py
from batou.lib.supervisor import Supervisor
environments/local.cfg (v4)
[environment]
connect_method = local

[hosts]
localhost = tick, supervisor

To make your program run within supervisor, you register it by configuring a program component:

components/myapp/component.py (v6)
from batou.component import Component, Attribute
from batou.lib.file import File
from batou.lib.supervisor import Program

class Tick(Component):

    sleep = Attribute(int, 1)

    def configure(self):
        self += File('tick.sh', mode=0755)
        self += Program('tick', command='tick.sh')

Deploying it enables supervisor in its own virtualenv and starts the registered program:

$ ./batou deploy local
batou/1.2b2 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `local`...
main: Verifying repository ...
main: Loading secrets ...
===================== Configuring first host =====================
localhost: Connecting via local (1/1)
=================== Connecting remaining hosts ===================
=========================== Deploying ============================
localhost: Deploying component supervisor ...
  Supervisor > Buildout > File(work/supervisor/buildout.cfg) >
    Presence(work/supervisor/buildout.cfg)
  Supervisor > Buildout > File(work/supervisor/buildout.cfg) >
    Content(work/supervisor/buildout.cfg)
  Supervisor > Buildout > VirtualEnv(2.7) > VirtualEnvPy2_7
  Supervisor > Buildout > VirtualEnv(2.7) >
    Package(setuptools==19.2)
  Supervisor > Buildout > VirtualEnv(2.7) >
    Package(zc.buildout==2.5.0)
  Supervisor > Buildout
  Supervisor > Directory(work/supervisor/etc/supervisor.d)
  Supervisor > File(work/supervisor/etc/supervisord.conf) >
    Presence(work/supervisor/etc/supervisord.conf)
  Supervisor > File(work/supervisor/etc/supervisord.conf) >
    Content(work/supervisor/etc/supervisord.conf)
  Supervisor > Directory(work/supervisor/var/log)
  Supervisor > RunningSupervisor
localhost: Deploying component tick ...
  Tick > Program(tick) > File(work/supervisor/etc/supervisor.d/tick.conf)
    > Presence(work/supervisor/etc/supervisor.d/tick.conf)
  Tick > Program(tick) > File(work/supervisor/etc/supervisor.d/tick.conf)
    > Content(work/supervisor/etc/supervisor.d/tick.conf)
  Tick > Program(tick)
====================== DEPLOYMENT FINISHED =======================
$ ./work/supervisor/bin/supervisorctl
tick                             RUNNING   pid 30992, uptime 0:01:37
supervisor> ^D

Working with network addresses

Network addresses in batou typically appear in various config files: for clients to find their servers and for servers to configure their bind addresses.

A good practice is to use hostnames when configuring a client, so the IP of the server can be dynamically resolved when connecting. For servers, bind addresses should be configured with IPs because resolvers may not be reachable at the time the server is started and the server may not start reliably under that condition.

batou has a utility object Address that can be used for doing DNS lookups depending on what you’re doing:

>>> from batou.utils import Address
>>> address = Address('localhost', 8080)
>>> str(address.listen)
'127.0.0.1:8080'
>>> str(address.connect)
'localhost:8080'

You can also use the Address type for converting overrides automatically:

from batou.component import Component, Attribute
from batou.utils import Address

class MyApp(Component):

    address = Attribute(Address, 'localhost:8080')

To have services talk to each other on different machines, you can use the host attribute of a component to get the name of the host that the component is configured on. Specifically we recommend using host.fqdn, which can be used with the built-in Jinja2 templating:

from batou.component import Component, Attribute
from batou.utils import Address

class MyApp(Component):

    address = Attribute(Address, '{{host.fqdn}}:8080')

    # address is now environment- and host-specific, e.g.:
    # address == Address('test01.fcio.net', 8080)

To override this attribute in an environment configuration you simply do a regular override using templating:

[component:myapp]
address = {{host.fqdn}}:9000

or static addresses:

[component:myapp]
address = 192.168.0.1:8080

Registering and discovering services

If you deploy your application on multiple hosts, you need to communicate between components what is configured where and how often. For example, to configure a load balancer, you need to see where the application servers are installed.

batou provides an API that allows a component to provide a resource and other components to require a resource. This API has the following features:

  • handling scalars (single values) and list-oriented resources
  • filtering for resources by key and by host
  • warning if provided resources are never used
  • warning if required resources are never provided
  • warning if multiple resources are provided where exactly one is expected

Additionally, batou orders components based on their resource requirements: a component that provides a resource will be deployed before the component requiring it. This has component granularity, so batou may switch deploying components between hosts if that is what the dependencies require.

To provide a resource, you call provide with a key (that you can define as you like.)

from batou.component import Component, Attribute
from batou.utils import Address

class MyApp(Component):

    address = Attribute(Address, '{{host.fqdn}}:8080')

    def configure(self):
        self.provide('application', self.address)

If you call provide from multiple components, then batou will automatically maintain a list of those items.

To get the registered resources for a key, you call require with the key you are interested in:

from batou.component import Component
from batou.lib.file import File

class Loadbalancer(Component):

    def configure(self):
        application_servers = self.require('application')

        self += File('loadbalancer', content='''\
{% for server in component.application_servers %}
server connect={{server.connect}}
{% endfor %}
''')

To filter for resources from the same host as your component and you expect a single value, you can do this:

from batou.component import Component

class Nginx(Component):

    def configure(self):
        varnish = self.require_one('varnish', host=self.host)

Note

You can pick keys as you like, but if you re-used standard components then you may need to consider collisions.

Checking a deployment configuration before running it

If you are ready to deploy something but want to wait until a certain point in time, then you can use -c (aka --consistency-only) with the deployment to see whether the configuration for the target environment is consistent without performing any action. This lets you debug your deployments early in the release cycle:

$ ./batou deploy -c production
batou/1.2b2 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `production`...
main: Verifying repository ...
main: Loading secrets ...
===================== Configuring first host =====================
test01: Connecting via ssh (1/1)
========================= CHECK FINISHED =========================

If the check fails you get the error message that you would otherwise have gotten when running the deployment for real:

$ ./batou deploy -c production
batou/1.2b2 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
=========================== Preparing ============================
main: Loading environment `production`...
main: Verifying repository ...
main: Loading secrets ...
===================== Configuring first host =====================
test01: Connecting via ssh (1/1)
ERROR: Overrides for undefined attributes
           Host: test01
      Component: Tick
     Attributes: foobar
ERROR: Unused provided resources
     supervisor: [<Supervisor (test01) "Supervisor">]
ERROR: 1 remaining unconfigured component(s)
================ 3 ERRORS - CONFIGURATION FAILED =================
========================== CHECK FAILED ==========================

Predicting the changes a deployment will cause

In addition to performing a consistency check, you can also perform a prediction which changes would during a deployment. This is more expensive than a pure consistency check, because it connects to all hosts and runs all verify() commands. However, it gives you a rough estimate of the changes that would happen.

$ ./batou deploy tutorial -P
batou/1.3b1.dev0 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
============================ Preparing ============================
main: Loading environment `tutorial`...
main: Verifying repository ...
main: Loading secrets ...
====================== Configuring first host =====================
localhost: Connecting via local (1/1)
==================== Connecting remaining hosts ===================
================== Predicting deployment actions ==================
localhost: Deploying component tick ...
     Tick > File(work/tick/tick.sh) > Content(work/tick/tick.sh)
localhost: Deploying component supervisor ...
     Supervisor
================== DEPLOYMENT PREDICTION FINISHED =================

The prediction assumes that an exception during verify() indicates that a previous component that had changes would have performed an action that would trigger the component with a failing verification to perform an update. Under very specific conditions this could also be a real error when deploying, but in most cases this is not true.

batou can not predict all possible changes, especially if you depend on timestamps of files on the disk that are updated due to performing an action (which doesn’t happen when predicting).

Remember, the prediction is intended to give you a rough estimate in which top- level components you can expect changes. There should always be an indicator for some change if a component is affected. After that you can consider the implications, e.g. to determine when would be a good time to run the deployment if you expect downtime.

Updating batou in an existing project

You can update batou to a specific version for your project with a single command:

$ ./batou update --version 1.2
Updating batou to 1.2
See the changelog at https://batou.readthedocs.io/en/latest/changes.html

$ git add batou
$ git commit -m 'Update batou to 1.2'

Now, check in the changed batou file and all your colleagues will get the new batou version automatically the next time the pull and run batou.

Advanced Usage

Writing a custom component (TODO)

Using 3rd party libraries within batou

Sometimes, when writing custom components, you may need additional Python packages, for example to configure databases by connecting directly to their SQL interface instead of using their command line clients.

You can use additional Python packages by adding a requirements.txt file to your batou project repository:

$ tree
.
├── batou
├── components
│   └── myapp
│       └── component.py
├── environments
│   └── local.cfg
└── requirements.txt
requirements.txt
sqlalchemy

The next time when you call batou the dependencies will be automatically updated. When deploying then the requirements will also be installed on the remote hosts.

$ ./batou
Installing sqlalchemy
usage: batou [-h] [-d] [-F] [--reset] {deploy,secrets,init,update} ...

Note

batou currently parses the requirements.txt by itself and only supports a sub-set of the official requirements.txt file format.

We support:

  • #egg links
  • -f
  • a simple form of + VCS links
  • all lines that can be passed directly as a requirement to pip install
  • comments

Note

batou already provides a number of packages that it depends on. If you create contradicting requirements then this may lead to batou failing. You will see pip complaining in that case.

Multiple components in a single component.py (TODO)

Skipping individual hosts or components when deploying (TODO)

Events (TODO)

Using bundle transfers if the repository server is not reachable from your remote server (TODO)

Timeout (TODO)

VFS mapping for development (TODO)

VFS mapping with explicit rewrite rules (TODO)

Extended service discovery options (TODO)

Platform-specific components

New in version 1.4.

Platform-specific components allow to customize behavior depending on the system or “platform” the target system runs as. Examples:

  • Production system on Gentoo, local development on Ubuntu, or
  • All VMs on Ubuntu but Oracle is being run with RedHat.

To define a platform specific aspects, you use the platform class decorator. Example:

import batou.component
import batou.lib.file


class Test(batou.component.Component):

    def configure(self):
        self += batou.lib.file.File('base-component')


@batou.component.platform('nixos', Test)
class TestNixos(batou.component.Component):

    def configure(self):
        self += batou.lib.file.File('i-am-nixos')


@batou.component.platform('ubuntu', Test)
class TestUbuntu(batou.component.Component):

    def configure(self):
        self += batou.lib.file.File('i-am-ubuntu')

The platform is then defined in the environment:

[environment]
platform = default-platform

[host:nixos]
# Host specifc override:
platform = nixos
components = test

[host:ubuntu]
# Host specifc override:
platform = ubuntu
components = test

Host-specific data

New in version 1.5.

Host-specifc data allows to set environment depentend data for a certain host. It looks like this in an environment configuration:

[host:myhost00]
components = test
data-alias = nice-alias.for.my.host.example.com

In a component you can access all data attributes via the host’s data dictionary:

def configure(self):
    alias = self.host.data['alias']

The data- prefix was chosen in resemblance of the HTML standard.

DNS overrides

New in version 1.6

When migrating services automatic DNS lookup of IP addresses to listen on can be cumbersome. You want to deploy the service before the DNS changes become active. This is where DNS overrides can help.

The DNS overrides short circuit the resolving completely for the given host names.

Example:

[environment]
...

[resolver]
www.example.com =
    3.2.1.4
    ::2

Whenever batou configuration (i.e. batou.utils.Address) looks up www.example.com it will result in the addresses 3.2.1.4 and ::2.

The overrides support IPv4 and IPv6. You should only set one IP address per type for each host name.

Note

You cannot override the addresses of the configured hosts. The SSH connection will always use genuine name resolving.

context manager (TODO)

last_updated (TODO)

prepare, |=, component._ (TODO)

workdir overriding (TODO)

batou.c (TODO)

ordered alphabetically (significant for imports)

Command line

If you are looking for information on what commands the batou CLI provides then this is for you.

Command Line Usage

General options

usage: batou [-h] [-d] [-F] {deploy,secrets,init,update} ...

batou v1.2b1 multi-(host|component|environment|version|platform) deployment

positional arguments:
  {deploy,remote,secrets,init,update}
    deploy              Deploy an environment.
    secrets             Manage encrypted secret files. Relies on gpg being
                        installed and configured correctly.
    init                Initialize batou project in the given directory. If
                        the given directory does not exist, it will be
                        created. If no directory is given, the current
                        directory is used.
    update              Update the batou version.

optional arguments:
  -h, --help            show this help message and exit
  -d, --debug           Enable debug mode. (default: False)
  -F, --fast            Enable fast mode. Do not perform bootstrapping.
                        (default: False)
  --reset               Reset batou environment. (default: False)

batou deploy

usage: batou deploy [-h] [-p PLATFORM] [-t TIMEOUT] [-D] [-c] [-P] environment

positional arguments:
  environment           Environment to deploy.

optional arguments:
  -h, --help            show this help message and exit
  -p PLATFORM, --platform PLATFORM
                        Alternative platform to choose. Empty for no platform.
  -t TIMEOUT, --timeout TIMEOUT
                        Override the environment's timeout setting
  -D, --dirty           Allow deploying with dirty working copy or outgoing
                        changes.
  -c, --consistency-only
                        Only perform a deployment model and environment
                        consistency check. Only connects to a single host.
                        Does not touch anything.
  -P, --predict-only    Only predict what updates would happen. Do not change
                        anything.

batou secrets edit

usage: batou secrets edit [-h] [--editor EDITOR] environment

positional arguments:
  environment           Environment to edit secrets for.

optional arguments:
  -h, --help            show this help message and exit
  --editor EDITOR, -e EDITOR
                        Invoke EDITOR to edit (default: $EDITOR or vi)

batou secrets overview

Show an overview of which users have access to what encrypted secrets.

usage: batou secrets summary [-h]

optional arguments:
  -h, --help  show this help message and exit

batou secrets add

usage: batou secrets add [-h] [--environments ENVIRONMENTS] keyid

positional arguments:
  keyid                 The user's key ID or email address

optional arguments:
  -h, --help            show this help message and exit
  --environments ENVIRONMENTS
                        The environments to update. Update all if not
                        specified.

batou secrets remove

usage: batou secrets remove [-h] [--environments ENVIRONMENTS] keyid

positional arguments:
  keyid                 The user's key ID or email address

optional arguments:
  -h, --help            show this help message and exit
  --environments ENVIRONMENTS
                        The environments to update. Update all if not
                        specified.

batou update

usage: batou update [-h] (--version VERSION | --develop DEVELOP)

optional arguments:
  -h, --help         show this help message and exit
  --version VERSION  Exact version to install.
  --develop DEVELOP  Path to checkout of batou to install in edit mode.

Components

This is the list of components that batou provides – builtin and through the batou_ext package:

Managing files and directories

Files and Templates

The File component has been developed with Puppet’s file type in mind. It accepts a very similar parameter set and has almost identical features.

You can use it to manage files, directories, and symlinks, and you can specify content (literally or as Jinja templates). You can also manage the Unix attributes and control whether leading directories should be managed or not.

The most basic usage is simply:

self += File('myfile')

This example creates a file at work/mycomponent/myfile, taking the contents from a file of the same name in the component’s directory (i.e. components/mycomponent/myfile). By default, the source file is run through Jinja, with the file’s parent component made available as component.

class batou.lib.File(path)

Creates a file. The main parameter for File is the target path. A File instance has an attribute path containing the full, absolute path to the resulting file.

File accepts the following additional parameters:

source

Filename of the source file to be used as the File’s content (absolute path or relative to the component’s directory). [Default: same as target path]

content

Literal file contents as a string.

is_template

Process file contents as Jinja template. [Default: True]

template_context

Object to make available as component to the Jinja template. [Default: File’s parent component]

template_args

Dict of additional arguments to make available to the Jinja template.

encoding

Encoding for the file contents [Default: utf-8]

owner

Unix owner username.

group

Unix group name.

mode

Unix permission mode (octal number, e.g. 0o755)

leading

Create leading directories that were given in the target path. [Default: False]

ensure

Type of object to be created: ‘file’, ‘directory’, or ‘symlink’. This is useful for complex situations (e.g. creating a symlink with special ownership), for simple situations it’s probably more readable to use Directory or Symlink.

Source of symlink (for ensure = ‘symlink’)

class batou.lib.BinaryFile(path)

Subclass of batou.lib.File. Creates a non-template binary file.

Directories

class batou.lib.file.Directory(path)

Creates a directory. The main parameter is the target path.

source

Path to a source directory whose contents are to be synchronized to the target path (uses rsync internally).

exclude

List of file names or patterns that should not be synchronized to the target path (passed to rsync as --exclude argument, see the rsync documentation for details).

Creates a symlink at target by linking to source.

Removing files

Removal of obsolete things is a difficult topic in the convergence paradigm. If in the past we created a file foo, but now it is not used anymore, the code that originally said, “please manage foo”, will not be there anymore. This means that nobody knows that the file foo that is still lying around on the production system is not actually in use anymore. In most cases, a few stray files do not matter, but in case they do, the deployment code has to explicitly state that something should not be present anymore.

class batou.lib.file.Purge(pattern)

Ensures that a set of files (given as a glob pattern) does not exist.

Extracting archive files

batou can extract archive files in Tar, Zip, and DMG (on OSX target machines) format:

class batou.lib.archive.Extract(archive)

The main parameter is the archive filename (relative to the component’s directory). The archive format is determined according to the file name extension (‘.tar’, ‘.tar.gz’, ‘.tgz’, ‘.tar.bz2’, ‘.tar.xz’ use tar, ‘.zip’ uses unzip and ‘.dmg’ uses hdiutil). The following additional parameters are supported:

target

Target directory to extract the archive into. Directory is created if it does not exist (compare create_target_dir). [Default: base name of the archive file]

create_target_dir

Extract into the directory given in target. Set to False to extract directly into the work directory. [Default: True]

strip

Only for tar archives: number of directories contained in the archive to strip off (see the tar documentation for details) [Default: 0]

VFS mapping (TODO)

XXX writeme

Downloads and VCS checkouts

Downloading files

batou supports downloading files via HTTP(S) or FTP, for example:

self += Download(
    'http://python.org/ftp/python/3.3.2/Python-3.3.2.tar.bz2',
    checksum='md5:7dffe775f3bea68a44f762a3490e5e28')
class batou.lib.download.Download(url)

Download from the given URL (uses urllib or requests internally).

requests_kwargs

Keyword arguments to pass to requests get method, e.g. to support authentication.

checksum

Checksum of the file to be verified (required). Must be given in the format algorithm:value, where algorithm must be a function of the hashlib stdlib module.

target

Filename to save the download as. [Default: last component of the URL]

Mercurial

self += Clone('https://bitbucket.org/gocept/batou', revision='tip')
class batou.lib.mercurial.Clone(url)

Clone a Mercurial repository from the given URL.

revision

Which revision to clone. At least one of revision or branch is required. If both are given, revision will be used.

branch

The name of a branch to clone. At least one of revision or branch is required. If both are given, branch will be overridden by revision. A clone of a named branch will be updated to the most recent upstream revision of the branch whenever batou is run.

target

Path to clone into (Default: workdir of parent component)

vcs_update

Whether to update the clone with incoming changesets (Default: True). Leaving clones of source code unchanged is often desirable during development.

Git

self += Clone('https://github.com/Pylons/pyramid', revision='HEAD')
class batou.lib.git.Clone(url)

Clone a Git repository from the given URL.

target

Path to clone into (Default: workdir of parent component)

update_unpinned

Update the clone on each batou run. If False, the repository is cloned once and then never updated again. [Default: False]

Note

git.Clone does not support specifying a revision yet.

Subversion

self += Checkout('https://svn.zope.org/repos/main/zopetoolkit/trunk', revision='130345')
class batou.lib.svn.Checkout(url)

Check out a Subversion repository from the given URL.

revision

Which revision to check out (required)

target

Path to clone into (Default: workdir of parent component)

Building software

batou has some support for downloading and compiling software packages, aka the configure-make-make install (CMMI) dance. Example usage:

self += Build(
    'http://python.org/ftp/python/3.3.2/Python-3.3.2.tar.bz2',
    checksum='md5:7dffe775f3bea68a44f762a3490e5e28',
    configure_args='--with-libs=-lssl')
class batou.lib.cmmi.Build(url)

Download archive from url, extract it and run CMMI on it.

checksum
Checksum for download (see batou.lib.download.Download.checksum for details)
prefix

Path to use as prefix for the installation (passed to configure --prefix) [Default: workdir of parent component]

configure_args

String of additional arguments to pass to configure.

build_environment

Dict of variables to add to the environment during all CMMI invocations.

Managing python installations

virtualenv

The basic building block for Python-based components is creation of virtualenvs (to separate package installations from each other):

self += VirtualEnv('2.7')
class batou.lib.python.VirtualEnv(version)

Creates a virtualenv for the given Python version in the working directory of the parent component. (Requires that pythonX.Y is in the PATH)

executable

Full path to the Python executable to create the virtualenv for (default: pythonX.Y based on the version attribute).

batou downloads a compatible version of virtualenv (depending on the Python version you need) to ensure everything works as expected and to avoid problems with incompatibilities or unexpected behaviours of whatever version might be installed already on the system. (virtualenv base installations are shared by all components for creating new virtualenvs, it is installed to work/.virtualenv).

Installing packages

Python packages are installed from a package index such as PyPI. batou uses pip or easy_install for this purpose (but that actually is an implementation detail and depends on the specifics of the Python and virtualenv version).

Packages must be added to a virtual environment.

venv = VirtualEnv('2.7')
self += venv
venv += Package('Sphinx', version='1.1.3')
class batou.lib.python.Package(package)

Install the Python package with the given name into the virtualenv of the parent component. Using Package requires that it is added to a VirtualEnv instance.

version

The version of the package to install (required).

install_options

List of options that are passed to pip/easy_install on the command line.

[Default: depends on the Python/virtualenv version in use]

check_package_is_module

Verify that the package is installed by trying to import it (more precisely, the first component of its dotted name). This is a stopgap against https://github.com/pypa/pip/issues/3, but should be pretty safe to disable if it causes trouble for specific packages (distribute is a notable example, since it installs a Python module named setuptools).

[Default: True]

timeout

A timeout (in seconds) that the installer should use to limit stalling network activity.

Only works when using pip.

[Default: equal to the environment’s timeout setting]

dependencies

Whether only the package itself or its dependencies should be installed.

[Default: True]

zc.buildout

batou has in-depth support for managing installations that use buildout. It automatically wraps them in a virtualenv, installs the appropriate buildout version, and takes care of running buildout whenever changes to configuration files makes it necessary. A typical usage example:

self += Buildout(python='2.7', version='2.2', setuptools='1.0',
                 additional_config=[Directory('profiles', source='profiles')])
class batou.lib.buildout.Buildout

Manage a buildout installation

python

Python version (required)

executable

Full path to the python executable to create the virtualenv for (used instead of pythonX.Y).

version

Version of zc.buildout to install (required)

setuptools

Version of setuptools to install into the virtualenv (must be appropriate to the buildout version, e.g. since 2.2 buildout requires setuptools, but some versions before that required distribute) (required)

distribute

Version of distribute to install into the virtualenv. Mutually exclusive with setuptools, of course.

config

If a different configuration file name than buildout.cfg should be used, pass in a File or Component instance.

additional_config

Optional list of component instances (e.g. File or Directory) that contain further configuration files (so Buildout knows when running buildout is needed).

Managing services

Aside from using batou’s general purpose functions for creating files and running commands we have a few ready-to-use abilities for higher level service management.

Supervisor (TODO)

Our built-in supervisor component allows you to run a supervisor process within your service user and has a simple API for declaring components that want to integrate with the supervisor config.

The supervisor itself will be integrated into the system’s startup automatically, depending on your platform.

SystemD

Note

SystemD is a non-core component provided through the batou_ext package.

Alternatively to using Supervisor you can register each program as a system-wide service managed by SystemD. You can also specify custom configuration in addition to (or overriding) the defaults:

from batou.component import Component
from batou.lib.file import File
from batou.lib.service import Service
import batou_ext.nix


class Tick(Component):

    def configure(self):
        self += File('tick.sh', mode=0755)

        self += Service('tick.sh',
            systemd=dict(Type='Simple',
                         Unit_After='cron.service memcached.service',
                         Service_RestartSec=11))

This will result in the following unit file:

/etc/local/systemd/tick.service
  [Service]
  Environment="LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive"
  Environment=PATH=/home/ctheune/bin:/var/setuid-wrappers:/home/ctheune/.nix-profile/bin:/home/ctheune/.nix-profile/sbin:/home/ctheune/.nix-profile/lib/kde4/libexec:/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/nix/var/nix/profiles/default/lib/kde4/libexec:/run/current-system/sw/bin:/run/current-system/sw/sbin:/run/current-system/sw/lib/kde4/libexec
  Environment="TZDIR=/etc/zoneinfo"
  ExecStart=/home/ctheune/deployment/work/tick/foobar.sh start
  Group=service
  LimitNOFILE=64000
  LimitNPROC=64173
  LimitSIGPENDING=64173
  RestartSec=11
  Type=Simple
  User=ctheune

  [Unit]
  After=cron.service memcached.service

If you want to leverage SystemD’s ability to repeat a key in the configuration (like using multiple ExecStart statements) then you can simply pass that key as a list. This will be automatically expanded into multiple lines:

systemd=dict(Type='Simple',
             ExecStart=['command1', 'command2'])
[Service]
...
ExecStart=/home/ctheune/deployment/work/tick/command1
ExecStart=/home/ctheune/deployment/work/tick/command2
...

Note

The SystemD support is currently geared towards the NixOS-based environment provided by us on our Flying Circus platform. We’re happy to extend and generalise this module upon request.

API

If you are looking for information on a specific function, class or method, this part of the documentation is for you.

Component Python API

This part of the documentation covers all the interfaces of batou you can use to develop your deployments.

Component

Attribute (TODO)

Host (TODO)

Environment (TODO)

Environment configuration

Component assignment (TODO)

General parameters (TODO)

General environment parameters are set in the [environment] config section. Example:

[environment]
service_user = website
host_domain = gocept.net
platform = gocept
branch = production
service_user
The deployment is run as this user on remote machines. If this is not the same as the user connecting via ssh, a sudo to the service user is performed.
host_domain
All hosts in the [hosts] section are postfixed with this domain. This is handy do make the host/component assignment less verbose
update_method

hg-bundle|hg-pull|git-bundle|git-pull|rsync, sets how the remote deployment repository is updated.

  • pull, the default, uses hg/git clone and/or hg/git pull on the remote site.
  • bundle will copy the necessary changes as Mercurial/Git bundle, via the batou ssh link.
  • rsync will rsync the working copy. This is most useful in combination with the vagrant platform.
branch
For remote deployments, use this and only this branch. batou will complain if the local branch does not match the set branch in the environment.
platform
Set the platform for this environment.
timeout
Set the ssh connection timeout in seconds.
target_directory
Absolute path of the directory on remote machines where the remote deployment repository is stored. Supports tilde expansion. Default: ~/deployment.

vfs mapping (TODO)

Root-component attribute overrides (TODO)

Jinja2 templates (TODO)

Utilities

class batou.utils.Address(connect_address, port=None)

An internet service address that can be listened and connected to.

The constructor address is expected to be the address that can be connected to. The listen address will be computed automatically.

>>> x = Address('localhost', 80)
>>> str(x.connect)
'localhost:80'
>>> str(x.listen)
'127.0.0.1:80'
connect = None

The connect address as it should be used when configuring clients. This is a batou.utils.NetLoc object.

listen = None

The listen (or bind) address as it should be used when configuring servers. This is a batou.utils.NetLoc object.

listen_v6 = None

The IPv6 listen (or bind) address as it should be used when configuring servers. This is a batou.utils.NetLoc object or None, if there is no IPv6 address.

exception batou.utils.CmdExecutionError(cmd, returncode, stdout, stderr)
exception batou.utils.CycleError
class batou.utils.NetLoc(host, port=None)

A network location specified by host and port.

Network locations can automatically render an appropriate string representation:

>>> x = NetLoc('127.0.0.1')
>>> x.host
'127.0.0.1'
>>> x.port
None
>>> str(x)
'127.0.0.1'

>>> y = NetLoc('127.0.0.1', 80)
>>> str(y)
'127.0.0.1:80'
host = None

The host part of this network location. Can be a hostname or IP address.

port = None

The port of this network location. Can be None or an integer.

Contributor guide

If you want to contribute to the project, this part of the documentation is for you.

Contributor’s guide

If you’re reading this you’re probably interested in contributing to batou. First, we’d like to say: thank you! Open source projects live-and-die based on the support they receive from others, and the fact that you’re even considering supporting batou is very generous of you.

This document lays out guidelines and advice for contributing to batou. If you’re thinking of contributing, start by reading this thoroughly and getting a feel for how contributing to the project works. If you have any questions, feel free to reach out to Christian Theune, the primary maintainer.

The guide is split into sections based on the type of contribution you’re thinking of making, with a section that covers general guidelines for all contributors.

All Contributions

Be Cordial

Be cordial or be on your way.

batou has adopted this very important rule from the Requests library. This rule governs all forms of contribution, including reporting bugs or requesting features. This golden rule is be cordial or be on your way. All contributions are welcome, as long as everyone involved is treated with respect.

Get Early Feedback

If you are contributing, do not feel the need to sit on your contribution until it is perfectly polished and complete. It helps everyone involved for you to seek feedback as early as you possibly can. Submitting an early, unfinished version of your contribution for feedback in no way prejudices your chances of getting that contribution accepted, and can save you from putting a lot of work into a contribution that is not suitable for the project.

Contribution Suitability

The project maintainer has the last word on whether or not a contribution is suitable for batou. All contributions will be considered, but from time to time contributions will be rejected because they do not suit the project.

If your contribution is rejected, don’t despair! So long as you followed these guidelines, you’ll have a much better chance of getting your next contribution accepted.

Code Contributions

When contributing code, you’ll want to follow this checklist:

  1. Fork the repository on bitbucket.
  2. Run the tests to confirm they all pass on your system. If they don’t, you’ll need to investigate why they fail. If you’re unable to diagnose this yourself, raise it as a bug report by following the guidelines in this document.
  3. Write tests that demonstrate your bug or feature. Ensure that they fail. Make your change.
  4. Run the entire test suite again, confirming that all tests pass including the ones you just added.
  5. Send a Pull Request to the main repository’s master branch. Pull Requests are the expected method of code collaboration on this project.

The following sub-sections go into more detail on some of the points above.

Code Review

Contributions will not be merged until they’ve been code reviewed. You should implement any code review feedback unless you strongly object to it. In the event that you object to the code review feedback, you should make your case clearly and calmly. If, after doing so, the feedback is judged to still apply, you must either apply the feedback or withdraw your contribution.

New Contributors

If you are new or relatively new to Open Source, welcome! batou aims to be a gentle introduction to the world of Open Source. If you’re concerned about how best to contribute, please consider mailing a maintainer (listed above) and asking for help.

Please also check the “Get Early Feedback” section.

Documentation Contributions

Documentation improvements are always welcome! The documentation files live in the doc/ directory of the codebase. They’re written in reStructuredText, and use Sphinx to generate the full suite of documentation.

When contributing documentation, please attempt to follow the style of the documentation files. This means a soft-limit of 79 characters wide in your text files and a semi-formal prose style.

Bug Reports

Bug reports are hugely important! Before you raise one, though, please check through the bugtracker issues, both open and closed, to confirm that the bug hasn’t been reported before. Duplicate bug reports are a huge drain on the time of other contributors, and should be avoided as much as possible.

When reporting a bug, make sure to include the batou version and platform identifier, e.g.:

$ ./batou
batou/1.2 (CPython 2.7.10-final0, Darwin 15.3.0 x86_64)
...

Also, include console output, relevant component code and maybe environment configuration if reporting errors.

Feature Requests

batou is under development. We have a strong idea about our architecture, though.

If you believe there is a feature missing, feel free to raise a feature request, but please demonstrate the issue you want to solve instead of only suggesting a certain function or feature. We want batou’s architecture to remain as small and clean as possible and thus we’re heavily interested in understanding the problem you are trying to solve.

Code of Conduct

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language
  • Being respectful of differing viewpoints and experiences
  • Gracefully accepting constructive criticism
  • Focusing on what is best for the community
  • Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances
  • Trolling, insulting/derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others’ private information, such as a physical or electronic address, without explicit permission
  • Other conduct which could reasonably be considered inappropriate in a professional setting
Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mail@flyingcircus.io. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4

How to Help

batou is under active development, and contributions are more than welcome!

  1. Check for open issues or open a fresh issue to start a discussion around a bug.
  2. Fork the repository on Bitbucket and start making your changes to a new branch.
  3. Write a test which shows that the bug or feature works fine.
  4. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS.

Ideas

  • A continuous deployment server would be nice. I started experimenting with a new project that would be called “Aramaki”. Contact @theuni if you are interested.

  • Documentation improvements are always welcome. Especially if you’re missing something or would like to understand things. We’ll be happy to explain things in depth – a good deed would then be to improve our documentation.

  • Switching from the Component base class to an explicit API to avoid namespace collisions would be nice. The idea would be to create components using a class decorator:

    from batou import component
    
    @component
    class MyApp(object):
    
        def configure(self, b):
            self += File(...)
    
        def verify(self, b):
            b.assert_no_subcomponent_changes('touch asdf')
    
        def verify(self, b):
            b.cmd('rm -rf /')
    

    b is an API object bound to the component that provides the standard API. This way, you can use any names on your environment without the hassle of potential namespace collisions.

  • More pre-defined, reusable components are welcome. Check the batou_ext repository.

  • Supporting provisioning for platforms aside from Vagrant: e.g. Amazon, Flying Circus, …

Development Dependencies

We use zc.buildout to manage the batou build environment:

$ virtualenv --python=python2.7
$ bin/pip install zc.buildout
$ bin/buildout

Run py.test to verify that everything works fine:

$ bin/py.test
================= test session starts ==================
platform darwin -- Python 2.7.10 -- py-1.4.26 -- pytest-2.6.4
plugins: cache, capturelog, codecheckers, cov, timeout
collected 426 items

src/batou/__init__.py ..
src/batou/_output.py ..
src/batou/agent.py ..
src/batou/bootstrap.py ..
src/batou/buildout.py ..
src/batou/c.py ..
src/batou/component.py ..
...

Runtime Environments

batou currently supports CPython 2.7 on Unix platforms.

We may switch to Python 3 at some point, but due to the meta programming that we do we likely will not support 2 and 3 at the same time.

We will likely not accept patches that are related to Windows. We are open to good arguments, though.

Downstream Repackaging

batou is not intended for downstream repackaging.

Authors

batou is written and maintained by Christian Theune and various contributors:

The Flying Circus Team

Patches and Suggestions

  • Michael Howitz, gocept gmbh & co. kg
  • Thomas Lotze, gocept gmbh & co. kg
  • Sebastian Wehrmann, gocept gmbh & co. kg
  • Wolfgang “wosc” Schnerring
  • Gil Forcada Codinachs
  • Stefan Walluhn
  • Cillian de Roiste
  • Florian Schulze
  • Hervé Coatanhay
  • Jan-Jaap Driessen
  • Marius Gedminas

Support

batou itself is released “as is”. We hang around #batou in the Freenode IRC network. You can also report bugs to our bugtracker.

Commercial support

We will be happy to give you commercial support for batou: feature implementation, bug fixing, consulting, or education.

To get in touch, send an email to mail@flyingcircus.io and we’ll be happy to discuss your requirements.

Resources

Changelog

1.13.0 (2020-04-17)

  • Add ability to disable supervisor programs.

1.12.5 (2020-03-27)

  • Fix brown bag release: the test suite did _not_ complete succesfully for 1.12.4 and obviously things were broken. Connections to remote hosts now work properly again.

1.12.4 (2020-03-27)

  • Fix locking remote batou processes to avoid interfering with other users’ deployments. (#29)

1.12.3 (2020-03-05)

  • Install at least pip 10.0 so we officially support the @ url syntax from PEP 508 (#30)

1.12.2 (2020-02-17)

  • Fix virtualenv creation on Python 2.7 (#25). Thanks icemac.

1.12.1 (2020-01-08)

  • Fix updating direct references in requirements.txt (#22)

1.12.0 (2019-10-11)

  • Make fixtures reusable by calling pytest_plugins = 'batou.fixtures' in code depending on them instead of importing from conftest.py which is not suggested by pytest.
  • Add convenience functions to get active components for a host.
  • Security updates for multiple dependencies.

1.11.0 (2019-04-23)

  • Support IPv6-only addresses in batou.utils.Address

1.10.1 (2019-01-31)

  • Download virtualenv directly from github instead of using pip download. Using pip download makes no sense anymore, as does not only download anymore. (Thanks wosc)

1.10.0 (2018-11-28)

  • Add support for deploying via ‘test kitchen’ (http://kitchen.ci/)
  • Fixed deploying into “own” user (no service user defined in environment) when remote and local user names are different (#106809, wosc).
  • Use Python’s built-in -m venv to create virtualenvs for Python >=3.3 (#107117).

1.9.0 (2018-10-26)

  • Force clone of git repositories when the remote url changes (#106661).
  • Implement basic sorting for Address-objects allowing to sort list of addresses (#100499)

1.8.0 (2018-08-16)

  • Show stdout of rsync command at verification step of SyncDirectory when running in debug mode.
  • Add VirtualEnv3.6 to support Python3.6

1.7.5 (2018-04-05)

  • Fix compatability to pip>=10.0 by using the download command instead of install --download.

1.7.4 (2018-04-03)

  • Fix compatability to pip>=10.0 by no longer forcing eggs instead of wheels.

1.7.3 (2018-03-19)

  • Fix git-bundle deploy when there are no changes to the deployment repository (#100645)

1.7.2 (2018-03-01)

  • Fix batou.lib.file.Purge to be able to delete files again.

1.7.1 (2018-01-29)

  • Fix git deployment repository handling.

1.7.0 (2018-01-29)

  • git.Clone uses git reset instead of merge/checkout to get the defined
    state.
  • batou uses git reset instead of merge/checkout to update the remote deployment repository.
  • Improve error message when deploying a wrong branch.

1.6.1 (2017-11-27)

  • Log very large (100k+) templates as dangerous. We’ve seen Jinja/Python segfault when users started using massively large (8MiB+) template files and this really isn’t a good idea to start with. For example it increases computing time even at 100k quite a lot.

    We try to keep going, though. But in the case of segfaults the user sees a reasonable error message and knows what to do.

  • Improve error reporting for missing our superfluous resources.

  • Using File(‘foo’) with implicitly picking up the source foo was confusing as it also would create (and enforce!) an empty file silently if the source did not exist.

    File(‘foo’) now expects that the source foo exists and if you really want an empty file you have to explicitly say File(‘foo’, content=’‘) or if you don’t care about the content use Presence(‘foo’).

  • Using File(‘foo’) with implicitly picking up the source foo was confusing as it also would create (and enforce!) an empty file silently if the source did not exist.

    File(‘foo’) now expects that the source foo exists and if you really want an empty file you have to explicitly say File(‘foo’, content=’‘) or if you don’t care about the content use Presence(‘foo’).

  • Do not use ‘–always-copy’ in VirtualEnv’s any longer. This caused more pain and inconsistencies than it provided value.

1.6.0 (2017-11-07)

  • Fix self.log() after configure() when no log was emitted during configure()

  • The Purge component now includes directories.

    This is not made an option as previously trying to purge directories would have raised an error and thus made deployments unusable anyway. Relying on this would be considered at least very bad practice and was never promised anyway.

  • The rsync update method now deletes files on the target.

  • The supervisor component uses current versions of buildout and setuptools, so they still work after PyPI recently disabled http without ssl.

1.6.0a3 (2017-08-09)

  • Ignore link-local (fe80::/64) IPv6 addresses in batou.utils.Address.
  • Fix: Grant database.* when using mysql.Grant

1.6.0a2 (2017-07-04)

  • General update of batou’s dependencies.
  • Fix: Don’t update git.Clone when there are incoming changes but the revision is fixed.

1.6.0a1 (2017-07-03)

  • Add a way to override DNS lookup during configuration, to better support migration scenarios.

The change log of older releases is in Historical releases.

Presentations from conferences: