 (Image by Computerizer from Pixabay)
(Image by Computerizer from Pixabay) 
Hello!
My name is 
Evgeny Cherkin , I am a programmer of the development team at the mining company 
Polymetal .
Starting any large project you start to think: “What software is better to use for its maintenance?”. IT-project before the release of the next version goes through a series of stages. Well, when the chain of these stages is automated. By itself, the automated process of releasing a new version of an IT project is called 
Continuous Integration . 
BuildBot for us was a good helper, implementing this process.
')
In this article, I decided to present an overview of the capabilities of 
BuildBot . What is this software capable of? How to approach it and how to build with it normal EFFECTIVE WORKING RELATIONS? You can also apply our experience in your own home by creating a working service for assembling and testing your project on your machine.
1. Why BuildBot?
Earlier on habr-e, I met articles on the implementation of 
Continuous Integration using 
BuildBot . For example, 
this one seemed to me the most informative. There is another example - 
easier . These articles can be seasoned with 
an example from the manual , and 
this will follow in English. The coupe is a good starting point. After reading these articles, you probably immediately want to do something on the 
BuildBot .
Stop! Did anyone even use it in their projects? It turns out yes, 
many have applied it in their tasks. You can find 
examples of using 
BuildBot in the archives of Google codes.
So what is the logic of people using 
Buildbot ? After all, there are other tools: 
CruiseControl and 
Jenkins . I will answer this way. For most tasks, 
Jenkins really will be enough. In turn, the 
BuildBot is more adaptive, while the tasks there are solved as easily as in 
Jenkins . Choose you. But since we are looking for a tool for a developing target project, then why not choose the one that allows, building on simple steps, to get an assembly system that has interactivity and a unique interface.
For those whose target project is written in python, the question arises: “Why not choose an integration system that has a clear interface from the point of view of the language used in the project?”. And then it's time to present the benefits of 
BuildBot .
So, our “instrumental quartet”. For myself, I defined the four features of BuildBot :
- This is an open source framework under the GPL license.
- This is the use of python as a tool for configuring and describing the required actions.
- This is an opportunity to receive a response from the side of the machine on which the assembly takes place.
- This, finally, the minimum requirements for the host. Deployment requires python and twisted, and does not require a virtual machine and java-machine.
2. Concept led by BuildMaster

A central place in the task distribution architecture is 
BuildMaster . It is a service that:
- tracks changes in the project source tree
- sends commands that Worker should execute to build a project and test it
- notifies users of the results of actions performed
BuildMaster is configured through the 
master.cfg file. This file is in the root of 
BuildMaster . Later I will show how this root is created. By itself, the 
master.cfg file contains python, a script that uses 
BuildBot calls.
The next most important 
BuildBot object is called 
Worker . This service can be run on another host from another OS, and maybe on where 
BuildMaster . It can also exist in a specially prepared virtual environment with its own packages and variables. These virtual environments can be prepared using python utilities like 
vertualenv, venv .
BuildMaster transmits commands to each 
Worker , and that, in turn, executes them. That is, it turns out that the process of building and testing a project can go on a 
Worker running Windows and on another Worker running linux.
The checkout of project source codes occurs at each 
Worker .
3. Installation
So let's go. As a host, I will use Ubuntu 18.04. On it, I will put one 
BuildMaster -a and one 
Worker -a. But first you need to install python3.7:
sudo apt-get update sudo apt-get install python3.7 
For those who need python3.7.2 instead of 3.7.1, you can do the following:
 sudo apt-get update sudo apt-get software-properties-common sudo add-apt-repository ppa:deadsnakes/ppa sudo apt-get install python3.7 sudo ln -fs /usr/bin/python3.7 /usr/bin/python3 pip3 install --upgrade pip 
The next step is to install 
Twited and 
BuildBot , as well as packages that allow you to use the additional functionality of 
BuildBot -a.
 /*   sudo        /usr/local/lib/python3.7/dist-packages*/  
4. First steps
Time to create a 
BuildMaster . We will have it in the folder 
/ home / habr / master .
 mkdir master buildbot create-master master  
Next step. Create a 
Worker . We will have it in the folder 
/ home / habr / worker .
 mkdir worker buildbot-worker create-worker --umask=0o22 --keepalive=60 worker localhost:4000 yourWorkerName password 
When you start 
Worker , by default it will create a folder in 
/ home / habr / worker with the name of the project that is specified in 
master.cfg . And in the daddy with the name of the project he will create a 
build directory, and then he will 
checkout there . The work directory for the 
Worker will be the 
/ home / habr / yourProject / build directory.
"Golden KeyAnd now, for the sake of what I wrote the previous paragraph: the script that the 
Master will require the 
Worker to do remotely in this directory will not be executed, since the script does not have permissions to run. To remedy the situation, you need a key 
--umask = 0o22 , which imposes a ban on writing to this directory, but will leave the launch rights. And we just need it.
BuildMaster and 
Worker establish a connection between themselves. It happens that it breaks off and the 
Worker awaits a response from 
BuildMaster for a while. If no response is given, the connection is restarted. The key 
--keepalive = 60 is just needed in order to specify the time after which 
connect restarts.
5. Configuration. Step-by-step recipe
The 
BuildMaster configuration is maintained on the side of the machine, where we executed the 
create-master command. In our case, this is the directory 
/ home / habr / master . The configuration file 
master.cfg does not yet exist, but the team itself has already created the file 
master.cmg.sample . You must rename it in 
master.cfg.sample in 
master.cfg mv master.cfg.sample master.cfg 
Open this 
master.cfg . And analyze what it consists of. And then try to make your configuration file.
master.cfg c['change_source'] = [] c['change_source'].append(changes.GitPoller( 'git://github.com/buildbot/hello-world.git', workdir='gitpoller-workdir', branch='master', pollInterval=300)) c['schedulers'] = [] c['schedulers'].append(schedulers.SingleBranchScheduler( name="all", change_filter=util.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=["runtests"])) c['schedulers'].append(schedulers.ForceScheduler( name="force", builderNames=["runtests"])) factory = util.BuildFactory() factory.addStep(steps.Git(repourl='git://github.com/buildbot/hello-world.git', mode='incremental')) factory.addStep(steps.ShellCommand(command=["trial", "hello"], env={"PYTHONPATH": "."})) c['builders'] = [] c['builders'].append( util.BuilderConfig(name="runtests", workernames=["example-worker"], factory=factory)) c['services'] = [] c['title'] = "Hello World CI" c['titleURL'] = "https://buildbot.imtqy.com/hello-world/" c['buildbotURL'] = "http://localhost:8010/" c['www'] = dict(port=8010, plugins=dict(waterfall_view={}, console_view={}, grid_view={})) c['db'] = { 'db_url' : "sqlite:///state.sqlite", } 
 5.1 BuildmasterConfig
 c = BuildmasterConfig = {} 
BuildmasterConfig - the basic dictionary of the configuration file. It must be necessarily involved in the configuration file. For ease of use in the configuration code is entered its alias 
"c" . 
Key names in 
c [“keyFromDist”] are fixed elements for interacting with 
BuildMaster . Under each key, the corresponding object is substituted as a value.
5.2 workers
 c['workers'] = [worker.Worker("example-worker", "pass")] 
This time we specify the 
BuildMaster list of 
Workers . We created the 
Worker above , specifying 
you-worker-name and 
password . Now they need to be specified instead of 
example-worker and 
pass .
5.3 change_source
 c['change_source'] = [] c['change_source'].append(changes.GitPoller( 'git://github.com/buildbot/hello-world.git', workdir='gitpoller-workdir', branch='master', pollInterval=300)) 
Using the change_source key of the 
c dictionary, we get access to the list where you want to put the object that polls the repository with the project source code. The example uses the Git repository, which is polled with a certain frequency.
The first argument is the path to your repository.
workdir is the path to the folder where, on the 
Worker side, relative to the path 
/ home / habr / worker / yourProject / build, git will store the local version of the repository.
branch contains a specific branch in the repository, which should be monitored.
pollInterval contains the number of seconds after which the 
BuildMaster will poll the repository for changes.
There are several methods to track changes in the project repository.
The easiest method is 
Polling , which implies that 
BuildMaster periodically polls the server with the repository. If the 
commit reflected the changes in the repository, then 
BuildMaster with some delay will create an internal 
Change object and send it to the 
Scheduler event handler, which will launch the steps to build and test the project on the 
Worker . Among these steps will be specified 
update repository. It is on 
Worker that a local copy of the repository will be created. The details of this process will be explained below in the next two sections 
( 5.4 and 5.5 ) .
An even more elegant method of tracking changes in the repository is direct sending messages from the server on which it is hosted to 
BuildMaster — about changing the source codes of a project. In this case, as soon as the developer makes a 
commit , the server with the project repository will send a message to 
BuildMaster . And that, in turn, will be intercepted by creating a 
PBChangeSource object. Further, this object will be transferred to 
Scheduler , which activates the steps for building the project and testing it. An important part of this method is working with server hooks in the repository. In the 
hook script, which is responsible for handling actions during 
commit , you must call the 
sendchange utility and specify the network address of 
BuildMaster . You need to 
specify the network port that the 
PBChangeSource will listen to. 
PBChangeSource , by the way, is part of 
BuildMaster . This method will require 
admin -a privileges on the server where the project repository is located. You must first make a backup repository.
5.4 shedulers
 c['schedulers'] = [] c['schedulers'].append(schedulers.SingleBranchScheduler( name="all", change_filter=util.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=["runtests"])) c['schedulers'].append(schedulers.ForceScheduler( name="force", builderNames=["runtests"])) 
schedulers is an element that acts as a trigger that runs the entire chain of assembly and testing of the project.

Those changes that were committed by 
change_source were transformed during the work of the 
BuildBot -a into a 
Change object, and now each 
Sheduler builds requests for starting the project build process on their basis. However, it also determines when to send these requests further in the queue. An object 
Builder keeps a queue of requests and monitors the status of the current assembly on a separate 
Worker- e. 
Builder exists on 
both BuildMaster -e and 
Worker -e. He also sends from 
BuildMaster to 
Worker a specific 
build , a series of steps that should be performed.
We see that in the current example of such 
schedulers 2 pieces are created. Moreover, each has its own type.
SingleBranchScheduler is one of the most popular schedule classes. It observes one branch and is triggered by a fixed change in it. When he sees the changes, he can postpone sending the build request (postpone for the period specified in the 
treeStableTimer special parameter). The 
name specifies the name of the schedule that will be displayed in the 
BuildBot- web interface. In 
ChangeFilter, a filter is set, after passing which changes in the branch cause the schedule to send a build request. In 
builderNames , the name 
builder -a is specified, which we will specify later. The name in our case will be the same as the project name: 
yourProject .
ForceScheduler is a very simple thing. This type of schedule is triggered by a mouse click through the 
BuildBot -web interface. Parameters have the same essence as in 
SingleBranchScheduler .
PS number 3. Suddenly usefulPeriodic is a schedule that is triggered at a certain fixed time interval. It looks like this
 from buildbot.plugins import schedulers nightly = schedulers.Periodic(name="daily", builderNames=["full-solaris"], periodicBuildTimer=24*60*60) c['schedulers'] = [nightly] 
5.5 BuildFactory
 factory = util.BuildFactory() factory.addStep(steps.Git(repourl='git://github.com/buildbot/hello-world.git', mode='incremental')) factory.addStep(steps.ShellCommand(command=["trial", "hello"], env={"PYTHONPATH": "."})) 
periodicBuildTimer sets the time of this periodicity in seconds.
BuildFactory creates a specific 
build , which the 
builder then sends to the 
Worker . The 
BuildFactory indicates the steps to be performed by the 
Worker . Steps are added by calling the 
addStep method 
. 
The first step added in this example is 
git clean -d -f -f –x , then 
git checkout . These actions are included in the 
method parameter, which is not clearly indicated, but implies the default value of 
fresh . The parameter 
mode = 'incremental' says that the files from the directory where 
chechout is being 
made , while those that are missing from the repository, remain intact.
The second step added is to call the 
trial script with the 
hello parameter on the 
Worker side from the 
/ home / habr / worker / yourProject / build directory with the PATHONPATH = environment variable ... ... Thus, you can write your own scripts and execute them on the 
Worker -a side through the 
util.ShellCommand step. These scripts can be put directly into the repository. Then when 
chechout -e they will fall into 
/ home / habr / worker / yourProject / build . However, then there are two "but":
- The Worker must be created with the --umask switch so that it does not block execution rights after checkout -a.
- When git push these scripts, you must specify the exacutable property, so that when chechout -e you do not lose the right to execute the Git script.
5.6 builders
 c['builders'] = [] c['builders'].append(util.BuilderConfig(name="runtests", workernames=["example-worker"], factory=factory)) 
About what 
Builder was told 
here . Now I will tell you more about how to create it. 
BuilderConfig is a 
builder constructor. There are several such constructors in 
c ['builders'] , since this is a list of 
builder type objects. Now let's rewrite the example from 
BuildBot , bringing it closer to our task.
 c['builders'] = [] c['builders'].append(util.BuilderConfig(name="yourProject", workernames=["yourWorkerName"], factory=factory)) 
Now I will tell you about the parameters 
BuilderConfig .
name specifies the name of the 
builder -a. Here we called it 
yourProject . This means that on the 
Worker , this same path will be created 
/ home / habr / worker / yourProject / build . 
Sheduler is looking for a 
builder by that name.
workernames contains a list of 
workers . Each of which must be added to 
c ['workers'] .
factory - a specific 
build with which the 
builder is associated. It will send the 
build object to the 
Worker to perform all the steps that make up this 
build -a.
6. Example of custom configuration
Here is the sample project architecture that I propose to implement via 
BuildBot 
.
As a version control system, we will use 
svn . The repository itself will be located in a certain cloud. Here is the address of this cloud 
svn.host/svn/yourProject/trunk . In the cloud under 
svn there is a username: 
user account, passwd: 
password . Scripts that are 
build -a steps will also be located in the 
svn branch, in a separate 
buildbot / worker_linux folder . These scripts are in the repository with the 
executable property saved.
BuildMaster and 
Worker work on the same 
project.host host. 
BuildMaster stores its files in the 
/ home / habr / master folder. 
Worker stores the following path 
/ home / habr / worker . 
BuildMaster -a and 
Worker- processes are 
connected through the 4000th port using the 
BuildBot -a protocol, that is, the 
'pb' protocol.
The target project is written entirely in python. The task is to track its changes, create an executable file, generate documentation, carry out testing. In case of failure, all developers need to send a message to the mail stating that there is a failed action.
Web mapping 
BuildBot we will connect to port 80 for 
project.host . Apatch is not required. The 
twisted library already contains a web server, 
BuildBot uses it.
To store the internal information for 
BuildBot, we will use 
sqlite .
For mailing, you need the 
smtp.your.domain host - it allows sending emails from 
projectHost@your.domain without authentication. Also, on the 
smtp host, the protocol is heard on post 1025.
There are two persons involved in the process: 
admin and 
user . admin admins 
BuildBot . user is the commit 
commit .
The exacutable file is generated via 
pyinstaller . Documentation is generated via 
doxygen .
For this architecture, I wrote this 
master.cfg :
master.cfg import os, re from buildbot.plugins import steps, util, schedulers, worker, changes, reporters c= BuildmasterConfig ={} c['workers'] = [ worker.Worker('yourWorkerName', 'password') ] c['protocols'] = {'pb': {'port': 4000}} svn_poller = changes.SVNPoller(repourl="https://svn.host/svn/yourProject/trunk", svnuser="user", svnpasswd="password", pollinterval=60, split_file=util.svn.split_file_alwaystrunk ) c['change_source'] = svn_poller hourlyscheduler = schedulers.SingleBranchScheduler( name="your-project-schedulers", change_filter=util.ChangeFilter(branch=None), builderNames=["yourProject"], properties = {'owner': 'admin'} ) c['schedulers'] = [hourlyscheduler] checkout = steps.SVN(repourl='https://svn.host/svn/yourProject/trunk', mode='full', method='fresh', username="user", password="password", haltOnFailure=True) projectHost_build = util.BuildFactory() cleanProject = steps.ShellCommand(name="Clean", command=["buildbot/worker_linux/pyinstaller_project", "clean"] ) buildProject = steps.ShellCommand(name="Build", command=["buildbot/worker_linux/pyinstaller_project", "build"] ) doxyProject = steps.ShellCommand(name="Update Docs", command=["buildbot/worker_linux/gendoc", []] ) testProject = steps.ShellCommand(name="Tests", command=["python","tests/utest.py"], env={'PYTHONPATH': '.'} ) projectHost_build.addStep(checkout) projectHost_build.addStep(cleanProject) projectHost_build.addStep(buildProject) projectHost_build.addStep(doxyProject) projectHost_build.addStep(testProject) c['builders'] = [ util.BuilderConfig(name="yourProject", workername='yourWorkerName', factory=projectHost_build) ] template_html=u'''\ <h4>  : {{ summary }}</h4> <p>   : {{ workername }}</p> <p>: {{ projects }}</p> <p>         : {{ buildbot_url }}</p> <p>         : {{ build_url }}</p> <p> WinSCP     c ip:xxx.xx.xxx.xx.   habr/password,   executable    ~/worker/yourProject/build/dist.</p> <p><b>    Buildbot</b></p> ''' sendMessageToAll = reporters.MailNotifier(fromaddr="projectHost@your.domain", sendToInterestedUsers=True, lookup="your.domain", relayhost="smtp.your.domain", smtpPort=1025, mode="warnings", extraRecipients=['user@your.domain'], messageFormatter=reporters.MessageFormatter( template=template_html, template_type='html', wantProperties=True, wantSteps=True) ) c['services'] = [sendMessageToAll] c['title'] = "The process of bulding" c['titleURL'] = "http://project.host:80/" c['buildbotURL'] = "http://project.host" c['www'] = dict(port=80, plugins=dict(waterfall_view={}, console_view={}, grid_view={})) c['db'] = { 'db_url' : "sqlite:///state.sqlite" } 
 First you need 
to create BuildMaster -a and 
Worker -a. Then paste this 
master.cfg file into 
/ home / habr / master .
The next step is to start the 
BuildMaster service 
. sudo buildbot start /home/habr/master 
Then start the 
Worker -a service.
 buildbot-worker start /home/habr/worker 
Done! Now 
Buildbot will track changes and work on a 
commit in 
svn , following the steps of building and testing a project with the above architecture.
Below I will write down some of the features of the above 
master.cfg. 
6.1 On the way to my master.cfg
A lot of mistakes will be made while writing your 
master.cfg , so you will need to read the log file. It is stored both on 
BuildMaster -ec in absolute way 
/home/habr/master/twistd.log , and on the 
Worker -a side with absolute way 
/home/habr/worker/twistd.log . As you read the error and fix it, you will need to restart the 
BuildMaster -a service. Here is how it is done:
 sudo buildbot stop /home/habr/master sudo buildbot upgrade-master /home/habr/master sudo buildbot start /home/habr/master 
6.2 Working with svn
 svn_poller = changes.SVNPoller(repourl="https://svn.host/svn/yourProject/trunk", svnuser="user", svnpasswd="password", pollinterval=60, split_file=util.svn.split_file_alwaystrunk ) c['change_source'] = svn_poller hourlyscheduler = schedulers.SingleBranchScheduler( name="your-project-schedulers", change_filter=util.ChangeFilter(branch=None), builderNames=["yourProject"], properties = {'owner': 'admin'} ) c['schedulers'] = [hourlyscheduler] checkout = steps.SVN(repourl='https://svn.host/svn/yourProject/trunk', mode='full', method='fresh', username="user", password="password", haltOnFailure=True) 
First, take a look at 
svn_poller . This is all the same interface that regularly polls the repository once a minute. In this case, 
svn_poller refers only to the 
trunk branch. The mysterious parameter 
split_file = util.svn.split_file_alwaystrunk sets the rules: how to split the 
svn folder structure into branches. He offers them relative paths. In turn, 
split_file_alwaystrunk simplifies the process by saying that only 
trunk is in the repository.
In 
Schedulers , a 
ChangeFilter is 
specified , which sees 
None and associates with it the 
trunk branch by the specified association through 
split_file_alwaystrunk . In response to changes in the 
trunk , it starts the 
builder with the name 
yourProject .
The properties here are needed in order for the admin to receive a mailing from the results of the assembly and testing as the owner of the process.
The 
build -a 
checkout step is capable of completely deleting any files in the local version of the 
Worker repository. A then do a full 
svn update . The mode is configured through the parameter 
mode = full , 
method = fresh . The 
haltOnTailure parameter says that if 
svn update is executed with an error, then the entire build and testing process should be suspended, since further actions do not make sense.
6.3 Letter to you: reporters authorized to declare
reporters is a mailing notification service.
 template_html=u'''\ <h4>  : {{ summary }}</h4> <p>   : {{ workername }}</p> <p>: {{ projects }}</p> <p>         : {{ buildbot_url }}</p> <p>         : {{ build_url }}</p> <p> WinSCP     c ip:xxx.xx.xxx.xx.   habr/password,   executable    ~/worker/yourProject/build/dist.</p> <p><b>    Buildbot</b></p> ''' sendMessageToAll = reporters.MailNotifier(fromaddr="projectHost@your.domain", sendToInterestedUsers=True, lookup="your.domain", relayhost="smtp.your.domain", smtpPort=1025, mode="warnings", extraRecipients=['user@your.domain'], messageFormatter=reporters.MessageFormatter( template=template_html, template_type='html', wantProperties=True, wantSteps=True) ) c['services'] = [sendMessageToAll] 
He can send messages in 
different ways .
MailNotifier uses mail to send notifications.
template_html specifies the template text to be distributed. To create markup is used html. It is modified by the 
jinja2 engine (can be compared with 
django ). 
BuildBot has a set of variables, the values ​​of which are substituted into the template during the formation of the message text. These variables are inscribed in {{double braces}}. For example, 
summary displays the status of completed operations, that is, success or failure. And the 
projects will display 
yourProject . So, with the help of control commands in 
jinja2 , 
BuildBot variables and python string formatting tools, you can create a completely informative message.
MailNotifier contains the following arguments.
fromaddr - address from which all will receive mailing.
sendToInterestedUsers = True sends a message to the owner and the user who made the 
commit .
lookup is the suffix to add to the names of users who receive the feed. So 
admin as the user will receive the newsletter at admin@your.domain.
relayhost sets the name of the host on which the 
smtp server is open, and 
smptPort sets the port number that the 
smtp server listens to.
mode = "warning" says that the distribution should be done only if there is at least one build step , which ended with the status of failure or warning. In the case of success, the mailing list is not required.extraRecipients contains a list of persons to whom the mailing list should be sent, in addition to the owner and the person who made the commit .messageFormatter is an object that defines the message format, its template, and the set of variables available from jinja2 . Parameters such as wantProperties = True and wantSteps = True set this set of available variables.with ['services'] = [sendMessageToAll] provides a list of services, among which will be ourreporter .We did it! My congratulations
We created our own configuration and saw the functionality that BuildBot is capable of . This, I think, is enough to understand whether this tool is needed to create your project. Is he interesting to you? Will it be useful to you? Is it convenient to work with him? Then I write this article for good reason.And further.
It would be great if the professional community using the BuildBot becomes wider, the manuals are translated, and there are more examples.Thank you all for your attention. Good luck.