Buildbot en los ejemplos

Necesitaba configurar el proceso de compilación y entrega de paquetes de software desde un repositorio de Git en el sitio. Y al ver, no hace mucho, un artículo aquí en Habr sobre buildbot (enlace al final), decidí probarlo y aplicarlo.

Dado que buildbot es un sistema distribuido, será lógico crear un hospedaje de compilación separado para cada arquitectura y sistema operativo. En nuestro caso, estos serán contenedores LXC (para Linux) y qemu (para Windows):

  • vm-srv-build1 — centos 7, aquí estará el maestro (master) de buildbot y uno de los trabajadores (worker)
  • vm-srv-build2 — debian 10, para la compilación de paquetes DEB
  • vm-srv-build3 — windows 10, para la compilación, ya saben para qué

Vamos a compilar Rac GUI — una interfaz gráfica para 1C rac para gestionar un clúster de servidores. En Linux se utilizarán herramientas estándar para cada SO, y para compilar un archivo exe para Windows a partir de un script tcl se usa freewrap.

Instalación

GNU/Linux

Hay suficiente documentación en línea sobre la instalación. 1,2. Y no presenta problemas especiales:
Para el maestro:

pip3 install buildbot
pip3 install twisted
pip3 install autobahn
pip3 install pysqlite3
pip3 install sqlalchemy sqlalchemy-migrate
pip3 install buildbot-www buildbot-grid-view buildbot-console-view buildbot-waterfall-view
pip3 install python-dateutil

Para los «trabajadores», esto es suficiente:

pip3 install buildbot-worker

Por supuesto, sería más correcto compilar paquetes para cada SO, pero eso no se incluye en el marco de este artículo. También omitiremos la descripción de la configuración de los contenedores para el trabajo, solo mencionaré que utilizo ProxMox VE. Y también será necesario instalar paquetes requeridos para la compilación en cada sistema (centos: rpmdevtools, etc.; debian: build-essential, dh-make, pbuilder, etc.)

La compilación de proyectos y los servicios de buildbot se ejecutarán con los privilegios de un usuario no privilegiado, por lo que es necesario crear uno en todos los hosts que participen en el proceso:


adduser buildbot

Luego configuraremos el inicio automático de los servicios, respectivamente en cada uno de los hosts (contenedores):

Unidad de Systemd para iniciar el maestro:

touch /etc/systemd/buildbot-master.service 

[Unit]
Description=Servicio maestro de BuildBot
After=network.target

[Service]
User=buildbot
Group=buildbot
WorkingDirectory=/home/buildbot/master
ExecStart=/usr/local/bin/buildbot start --nodaemon
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

y para el «trabajador»

touch /etc/systemd/buildbot-worker.service 

[Unit]
Description=Servicio trabajador de BuildBot
After=network.target

[Service]master
User=buildbot
Group=buildbot
WorkingDirectory=/home/buildbot/worker
ExecStart=/usr/local/bin/buildbot-worker start --nodaemon

[Install]
WantedBy=buildbot-master.service

Dado que todos los scripts (en nuestro caso) están en /usr/local/, se debe especificar la ruta a ellos en las variables de entorno:

nano /root/.bash_profile

PATH=$PATH:$HOME/.local/bin:$HOME/bin:/usr/local/bin

Después de esto, se puede crear la infraestructura de directorios para los "trabajadores" (en todos los hosts), para ello se registra con el usuario buildbot y se ejecutan los siguientes comandos:

En el primer host vm-srv-build1:

su - buildbot
mkdir /home/buildbot/worker
cd ~
buildbot-worker create-worker --umask=0o22 --keepalive=60 worker vm-srv-build1:4000 CentOS 123456

En el segundo host vm-srv-build2:

su - buildbot
mkdir /home/buildbot/worker
cd ~
buildbot-worker create-worker --umask=0o22 --keepalive=60 worker vm-srv-build1:4000 Debian-10 123456

En los hosts trabajadores, se puede iniciar el servicio buildbot-worker

systemctl start buildbot-worker

MS Windows

Como "trabajador" para la compilación en Windows, se usará una máquina virtual con la última versión de Win10.
Para trabajar se necesitará:

Después de que se instale todo lo mencionado anteriormente, se puede instalar el propio buildbot:

pip install buildbot-worker

Crearemos el directorio de trabajo

md c:worker

Y lo iniciaremos

buildbot-worker start c:worker

Si todo funciona (ver log c:workertwistd.log), se puede registrar nuestro "trabajador" como un servicio, añadiendo al registro la entrada con el directorio de trabajo (los comandos se ejecutan en PowerShell ejecutado como administrador):

buildbot_worker_windows_service.exe --user VM-SRV-BUILD3buildbot --password 123456 --startup auto install
New-ItemProperty -path Registry::HKEY_LOCAL_MACHINESYSTEMCurrentControlSetservicesBuildBotParameters -Name directories -PropertyType String -Value c:worker

Y se puede iniciar el servicio

Start-Service buildbot

Con los "trabajadores" eso es todo, luego no se deben tocar, toda la gestión se realiza desde el maestro.

Configuración del maestro

Para empezar, crearemos la infraestructura para el maestro (en el host principal), para ello se registra con el usuario buildbot y se ejecutan los siguientes comandos:

su - buildbot
mkdir /home/buildbot/master
cd ~
buildbot create-master master

Para los paquetes listos, crearemos un directorio builds

mkdir /home/buildbot/builds

En el directorio /home/buildbot/master/ se creó el archivo master.cfg. Este archivo es un código en Python y contiene la descripción de todos los mecanismos de funcionamiento del sistema, con él trabajaremos más adelante.

nano /home/buildbot/master/master.cfg

import os, re
from buildbot.plugins import steps, util, schedulers, worker, changes, reporters

c= BuildmasterConfig ={}

# Descripción de nuestros trabajadores.
c['workers'] = [ worker.Worker('CentOS', '123456'), worker.Worker('Debian-10', '123456'), worker.Worker('Windows-10', '123456')]
c['protocols'] = {'pb': {'port': 4000}} 

# Indicación al maestro de qué repositorio monitorear
c['change_source'] = []
c['change_source'].append(changes.GitPoller(
       repourl = 'https://bitbucket.org/svk28/rac-gui.git',
       project = 'Rac-GUI',
       branches = True,
       pollInterval = 60
      )
)

# Servicio para iniciar la construcción
c['schedulers'] = []
c['schedulers'].append(schedulers.SingleBranchScheduler(
       name="Rac-GUI-schedulers",
       change_filter=util.ChangeFilter(branch='master'),
       builderNames=["Rac-GUI-RPM-builder", "Rac-GUI-DEB-builder", "Rac-GUI-WIN-builder"],
       properties = {'owner': 'admin'}
       )
)
@util.renderer

######################################3
# Construcción del paquete RPM
 rac_gui_build_RPM = util.BuildFactory()

rac_gui_build_RPM.addStep(steps.Git(
       repourl = 'https://bitbucket.org/svk28/rac-gui.git',
       workdir = 'rac-gui',
       haltOnFailure = True,
       submodules = True,
       mode='full',
       progress = True)
)

Para automatizar la construcción de paquetes de diferentes versiones, para no tener que modificar el código en el archivo master.cfg, se añadieron líneas con la versión actual y el lanzamiento en el script principal del programa rac_gui.tcl:

######################################################
#        Rac GUI
...
# version: 1.0.3
# release: 1

Y basado en estas líneas, buildbot numerará los paquetes. Para extraer los datos, se utiliza la llamada de consola grep. En buildbot no se pueden definir variables para los "trabajadores" así como así (al menos yo no encontré la forma). Para esto sirven las propiedades (property). Es decir, en el proceso de construcción, añadimos pasos para definir la versión y el lanzamiento y, en consecuencia, establecemos las propiedades version y release. Las propiedades se pueden establecer de diversas maneras, en este caso se trata de una llamada a un comando de consola:

# Добавим определение версии из основного файла
rac_gui_build_RPM.addStep(
       steps.SetPropertyFromCommand(
       command="grep version ../rac-gui/rac_gui.tcl | grep -oE 'b[0-9]{1,2}.[0-9]{1,2}.[0-9]{1,2}b'", property="version"
       )
)
# Добавим определение релиза из основного файла
rac_gui_build_RPM.addStep(
       steps.SetPropertyFromCommand(
       command="grep release ../rac-gui/rac_gui.tcl | grep -oE 'b[0-9]{1,3}b'", property="release"
       )
)

Los valores obtenidos se pueden insertar mediante la llamada a util.Interpolate().

# Запакуем исходники
rac_gui_build_RPM.addStep(
       steps.ShellCommand(
          command=["tar", "czf", util.Interpolate("/home/buildbot/rpmbuild/SOURCES/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz"), "../rac-gui"]
       )
)

Aquí es importante notar que, dado que el host también se utiliza para la construcción manual de paquetes, la construcción se realizará a través de rutas estándar.

# Копируем spec
rac_gui_build_RPM.addStep(steps.ShellCommand(
       command=["cp", "../rac-gui/rac_gui.spec", "/home/buildbot/rpmbuild/SPECS/rac_gui.spec"]))

Para establecer correctamente los números de lanzamiento y versión se utiliza la llamada al estándar sed, es decir, el comando reemplaza los valores dentro del archivo spec con los necesarios.

# меняем версию
rac_gui_build_RPM.addStep(steps.ShellCommand(
       command=["sed", "-i", util.Interpolate("s/.*Version:.*/Version:t%(prop:version)s/"), "/home/buildbot/rpmbuild/SPECS/rac_gui.spec"]))
# меняем релиз
rac_gui_build_RPM.addStep(steps.ShellCommand(
       command=["sed", "-i", util.Interpolate("s/.*Release:.*/Release:t%(prop:release)s/"), "/home/buildbot/rpmbuild/SPECS/rac_gui.spec"]))

# запускаем процесс сборки
rac_gui_build_RPM.addStep(steps.RpmBuild(
       specfile="/home/buildbot/rpmbuild/SPECS/rac_gui.spec",
       dist='.el5',
       topdir='/home/buildbot/rpmbuild',
       builddir='/home/buildbot/rpmbuild/build',
       rpmdir='/home/buildbot/rpmbuild/RPMS',
       sourcedir='/home/buildbot/rpmbuild/SOURCES'
      )
)

El paquete construido y el archivo comprimido con los fuentes se copian al maestro. Pero también se puede copiar directamente desde los archivos de trabajo a su repositorio o al sitio.

# Скопируем файл на мастер
rac_gui_build_RPM.addStep(
       steps.FileUpload(
          workersrc=util.Interpolate("/home/buildbot/rpmbuild/RPMS/noarch/rac-gui-%(prop:version)s-%(prop:release)s.noarch.rpm"),
          masterdest=util.Interpolate("/home/buildbot/builds/rac-gui-%(prop:version)s-%(prop:release)s.noarch.rpm")
      )
)

rac_gui_build_RPM.addStep(
       steps.FileUpload(
       workersrc=util.Interpolate("/home/buildbot/rpmbuild/SOURCES/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz"),
          masterdest=util.Interpolate("/home/buildbot/builds/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz")
       )
)

Iniciaremos en el maestro el proceso de copiar los paquetes construidos al hosting a través de ftp. Para esto se utiliza script en tcl.

rac_gui_build_RPM.addStep(
        steps.MasterShellCommand(
            command=["/usr/local/bin/deploy-ftp.tcl",
                util.Interpolate("--local-file=/home/buildbot/builds/rac-gui-%(prop:version)s-%(prop:release)s.noarch.rpm"),
                util.Interpolate("--remote-file=uploads/rac-gui/rac-gui-%(prop:version)s-%(prop:release)s.noarch.rpm")]
        )
)

rac_gui_build_RPM.addStep(
        steps.MasterShellCommand(
            command=["/usr/local/bin/deploy-ftp.tcl",
                util.Interpolate("--local-file=/home/buildbot/builds/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz"),
                util.Interpolate("--remote-file=uploads/rac-gui/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz")]
        )
)

Hemos terminado con RPM. Ahora vamos a describir el algoritmo para la construcción del paquete DEB. Dado que los procesos de construcción de paquetes para diferentes sistemas son independientes entre sí, muchos pasos se repetirán.


rac_gui_build_DEB = util.BuildFactory()

rac_gui_build_DEB.addStep(steps.Git(
       repourl = 'https://bitbucket.org/svk28/rac-gui.git',
       haltOnFailure = True,
       submodules = True,
       mode='full',
       workdir='build',
       progress = True)
)

# Agregaremos la definición de versión desde el archivo principal
rac_gui_build_DEB.addStep(
       steps.SetPropertyFromCommand(
       command="grep version rac_gui.tcl | grep -oE 'b[0-9]{1,2}.[0-9]{1,2}.[0-9]{1,2}b'", property="version"
       )
)

# Agregaremos la definición de lanzamiento desde el archivo principal
rac_gui_build_DEB.addStep(
       steps.SetPropertyFromCommand(
       command="grep release rac_gui.tcl | grep -oE 'b[0-9]{1,3}b'", property="release"
       )
)

# Renombramos el archivo ejecutable
    rac_gui_build_DEB.addStep(steps.ShellCommand(
       command=["mv", "rac_gui.tcl", "racgui"]))

Para el paquete RPM, parte de los procedimientos a continuación se realizan por rpm durante la construcción y se describen dentro del archivo de especificación; para Debian, tenemos que hacerlo aquí:

# Поменяем пути к библиотекам
rac_gui_build_DEB.addStep(steps.ShellCommand(
       command=["sed", "-i", "s+^set dir(lib)+set dir(lib) /usr/share/rac-gui/lib ;#+g", "racgui"]))

# Поменяем пути к файлам
rac_gui_build_DEB.addStep(steps.ShellCommand(
       command=["sed", "-i", "s+[pwd]+/usr/share/rac-gui+g", "racgui"]))

# заархивируем исходники
rac_gui_build_DEB.addStep(steps.ShellCommand(
       command=["tar", "czf", util.Interpolate("../rac-gui_%(prop:version)s.orig.tar.gz"), "."]))

# Соберём пакет
rac_gui_build_DEB.addStep(steps.ShellCommand(
       command=["dpkg-buildpackage"]))

# Скопируем файл на мастер
rac_gui_build_DEB.addStep(
       steps.FileUpload(
       workersrc=util.Interpolate("../rac-gui_%(prop:version)s-%(prop:release)s_amd64.deb"),
           masterdest=util.Interpolate("/home/buildbot/builds/rac-gui_%(prop:version)s-%(prop:release)s_amd64.deb")
       )
)
rac_gui_build_DEB.addStep(
        steps.MasterShellCommand(
            command=["/usr/local/bin/deploy-ftp.tcl",
             util.Interpolate("--local-file=/home/buildbot/builds/rac-gui_%(prop:version)s-%(prop:release)s_amd64.deb"),
             util.Interpolate("--remote-file=uploads/rac-gui/rac-gui_%(prop:version)s-%(prop:release)s_amd64.deb")]
        )
)

Y con DEB hemos terminado, ¡ahora vamos a Windows!

rac_gui_build_WIN = util.BuildFactory()

rac_gui_build_WIN.addStep(steps.Git(
       repourl = 'https://bitbucket.org/svk28/rac-gui.git',
       haltOnFailure = True,
       submodules = True,
       mode='full',
       workdir='build',
       progress = True)
)

Dado que en Windows no hay grep y sed por defecto (¿o sí?), utilizaremos PowerShell.

# Добавим определение версии из основного файла
rac_gui_build_WIN.addStep(
       steps.SetPropertyFromCommand(
       command="powershell -command "((Get-Content .rac_gui.tcl | Select-String -Pattern 'version:') -split 's')[2]",
       property="version"
       )
    )

# Добавим определение релиза из основного файла
rac_gui_build_WIN.addStep(
       steps.SetPropertyFromCommand(
       command="powershell -command "((Get-Content .rac_gui.tcl | Select-String -Pattern 'release:') -split 's')[2]",
       property="release"
       )
)

# Создадим запускаемый файл
rac_gui_build_WIN.addStep(steps.ShellCommand(
       command=["c:binfreewrap.exe", "rac_gui.tcl"]))

# запакуем то, что получилось
rac_gui_build_WIN.addStep(steps.ShellCommand(
       command=["c:Program Files7-zip7z.exe", "a", "-r", util.Interpolate("..rac-gui_%(prop:version)s-%(prop:release)s.win.zip"), "..build"]))

# скопируем на мастер
rac_gui_build_WIN.addStep(
       steps.FileUpload(
       workersrc=util.Interpolate("..rac-gui_%(prop:version)s-%(prop:release)s.win.zip"),
           masterdest=util.Interpolate("/home/buildbot/builds/rac-gui_%(prop:version)s-%(prop:release)s.win.zip")
       )
)
# Скопируем файл на хостинг
rac_gui_build_WIN.addStep(
        steps.MasterShellCommand(
            command=["/usr/local/bin/deploy-ftp.tcl",
            util.Interpolate("--local-file=/home/buildbot/builds/rac-gui_%(prop:version)s-%(prop:release)s.win.zip"),
            util.Interpolate("--remote-file=uploads/rac-gui/rac-gui_%(prop:version)s-%(prop:release)s.win.zip")]
       )
)

# Тут определяем какие сборщики у нас есть
c['builders'] = [
        util.BuilderConfig(name="Rac-GUI-RPM-builder", workername='CentOS', factory=rac_gui_build_RPM),
        util.BuilderConfig(name="Rac-GUI-DEB-builder", workername='Debian-10', factory=rac_gui_build_DEB),
        util.BuilderConfig(name="Rac-GUI-WIN-builder", workername='Windows-10', factory=rac_gui_build_WIN),
]

Para notificar el estado del proceso de construcción, utilizaremos correo electrónico.


c['services'] = []

template=u'''
    <h4>Estado de construcción: {{ summary }}</h4>
    <p> Trabajador utilizado: {{ workername }}</p>
    {% for step in build['steps'] %}
    <p> {{ step['name'] }}: {{ step['result'] }}</p>
    {% endfor %}
    <p><b> -- The Buildbot</b></p>
    '''

mailNotifier = reporters.MailNotifier(fromaddr="builder@domain.ru",
             sendToInterestedUsers=False,
             mode=('all'),
             extraRecipients=["admin@domain.ru"],
             relayhost="mail.domain.ru",
             smtpPort=587,
             smtpUser="builder@domain.ru",
             smtpPassword="******",
             messageFormatter=reporters.MessageFormatter(
                                 template=template, template_type='html',
                                 wantProperties=True, wantSteps=True))

c['services'].append(mailNotifier)

# Configuraciones principales del maestro
c['title'] = "El proceso de construcción"
c['titleURL'] = "http://vm-srv-build1:80/"
c['buildbotURL'] = "http://vm-srv-build1/"
c['www'] = dict(port=80,
                plugins=dict(waterfall_view={}, console_view={}, grid_view={}))
c['db'] = {
       'db_url' : "sqlite:////state.sqlite"
}

Guardamos el archivo y podemos intentar iniciar el servicio maestro:

systemctl restart buildbot-master

En el log, controlaremos que la configuración esté bien y que todo funcione normalmente. Todos nuestros trabajadores deberían conectarse ahora, lo que se comunicará alegremente en el log »»'/home/buildbot/master/twistd.log'»».

 2019-07-24 16:50:35+0300 [-] Cargando buildbot.tac...
 2019-07-24 16:50:35+0300 [-] Cargado.
 2019-07-24 16:50:35+0300 [-] twistd 19.2.1 (\/usr\/bin\/python3.6 3.6.8) iniciando.
 2019-07-24 16:50:35+0300 [-] clase del reactor: twisted.internet.epollreactor.EPollReactor.
 2019-07-24 16:50:35+0300 [-] Iniciando BuildMaster -- buildbot.version: 2.3.1
 2019-07-24 16:50:35+0300 [-] Cargando configuración desde '\/home\/buildbot\/master\/master.cfg'
 2019-07-24 16:50:36+0300 [-] \/usr\/local\/lib\/python3.6\/site-packages\/buildbot\/config.py:90: buildbot.config.ConfigWarning: [0.9.0 y posteriores] `buildbotNetUsageData` no está configurado y por defecto es básico.
        Este parámetro ayuda al equipo de desarrollo de buildbot a entender la base de instalación.
        No se recopila información personal.
        Solo se envía información sobre la versión del software de instalación y el uso de plugins.
        Puede `optar por no participar` estableciendo esta variable en None.
        O `optar por participar` para obtener más información estableciendo en "full".

 2019-07-24 16:50:36+0300 [-] Configurando base de datos con URL 'sqlite:\/state.sqlite'
 2019-07-24 16:50:36+0300 [-] estableciendo el modo de diario de la base de datos en 'wal'
 2019-07-24 16:50:36+0300 [-] añadiendo 1 nuevo servicio, eliminando 0
 2019-07-24 16:50:36+0300 [-] añadiendo 1 nueva fuente de cambio, eliminando 0
 2019-07-24 16:50:36+0300 [-] gitpoller: usando directorio de trabajo '\/home\/buildbot\/master\/gitpoller-work'
 2019-07-24 16:50:36+0300 [-] añadiendo 3 nuevos constructores, eliminando 0
 2019-07-24 16:50:36+0300 [-] añadiendo 1 nuevo programador, eliminando 0
 2019-07-24 16:50:36+0300 [-] inicializando plugin www 'waterfall_view'
 2019-07-24 16:50:36+0300 [-] inicializando plugin www 'console_view'
 2019-07-24 16:50:36+0300 [-] inicializando plugin www 'grid_view'
 2019-07-24 16:50:36+0300 [-] NOTA: el plugin www 'sitenav' está instalado pero no configurado
 2019-07-24 16:50:36+0300 [-] inicializando plugin www 'waterfall_view'
 2019-07-24 16:50:36+0300 [-] inicializando plugin www 'console_view'
 2019-07-24 16:50:36+0300 [-] inicializando plugin www 'grid_view'
 2019-07-24 16:50:36+0300 [-] NOTA: el plugin www 'sitenav' está instalado pero no configurado
 2019-07-24 16:50:36+0300 [-] BuildbotSite iniciando en 80
 2019-07-24 16:50:36+0300 [-] Iniciando fábrica <buildbot.www.service.BuildbotSite object at 0x7fe31c2657b8>
 2019-07-24 16:50:36+0300 [-] añadiendo 3 nuevos trabajadores, eliminando 0
 2019-07-24 16:50:36+0300 [-] PBServerFactory iniciando en 4000
 2019-07-24 16:50:36+0300 [-] Iniciando fábrica <twisted.spread.pb.PBServerFactory object at 0x7fe31c147470>
 2019-07-24 16:50:37+0300 [-] BuildMaster está en funcionamiento
 2019-07-24 16:50:37+0300 [-] buildbotNetUsageData: enviando {'installid': 'b6193b126b96689351d2fe95787c5a03fc0879f9', 'versions': {'Python': '3.6.8', 'Buildbot': '2.3.1', 'Twisted': '19.2.1'}, 'platform': {'platform': 'Linux-4.15.18-10- pve-x86_64-con-centos-7.6.1810-Core', 'system': 'Linux', 'machine': 'x86_64', 'processor': 'x86_64', 'python_implementation': 'CPython', 'version': '#1 SMP PVE 4.15.18-32', 'distro': 'centos:7'}, 'plugins': {'buildbot\/worker\/base\/Worker': 3, 'buildbot\/config\/BuilderConfig': 3, 'buildbot\/schedulers\/basic\/SingleBranchScheduler': 1, 'buildbot\/reporters\/mail\/MailNotifier': 1, 'buildbot\/changes\/gitpoller\/GitPoller': 1, 'buildbot\/steps\/worker\/MakeDirectory': 1, 'buildbot\/steps\/source\/git\/Git': 3, 'buildbot\/steps\/shell\/ShellCommand': 9, 'buildbot\/steps\/package\/rpm\/rpmbuild\/RpmBuild': 1}, 'db': 'sqlite', 'mq': 'simple', 'www_plugins': ['waterfall_view', 'console_view', 'grid_view']}
 2019-07-24 16:50:37+0300 [Broker,0,127.0.0.1] trabajador 'CentOS' conectándose desde IPv4Address(type='TCP', host='127.0.0.1', port=37332)
 2019-07-24 16:50:37+0300 [Broker,0,127.0.0.1] Obtuvimos información del trabajador de 'CentOS'
 2019-07-24 16:50:37+0300 [-] bot adjunto
 2019-07-24 16:50:37+0300 [Broker,0,127.0.0.1] Trabajador CentOS adjunto a Rac-GUI-RPM-builder
 2019-07-24 16:50:37+0300 [-] buildbotNetUsageData: buildbot.net dijo: ok
 2019-07-24 16:50:39+0300 [Broker,1,192.168.55.15] trabajador 'Windows-10' conectándose desde IPv4Address(type='TCP', host='192.168.5.145', port=49831)
 2019-07-24 16:50:39+0300 [Broker,1,192.168.55.15] Obtuvimos información del trabajador de 'Windows-10'
 2019-07-24 16:50:40+0300 [-] bot adjunto
 2019-07-24 16:50:40+0300 [Broker,1,192.168.55.15] Trabajador Windows-10 adjunto a Rac-GUI-WIN-builder
 2019-07-24 16:50:41+0300 [Broker,2,192.168.55.99] trabajador 'Debian-10' conectándose desde IPv4Address(type='TCP', host='192.168.5.9', port=44430)
 2019-07-24 16:50:41+0300 [Broker,2,192.168.55.99] Obtuvimos información del trabajador de 'Debian-10'
 2019-07-24 16:50:41+0300 [-] bot adjunto
 2019-07-24 16:50:41+0300 [Broker,2,192.168.55.99] Trabajador Debian-10 adjunto a Rac-GUI-DEB-builder

El proceso de configuración ha finalizado. Puedes consultar el estado actual a través de la interfaz web, donde también puedes ver errores de compilación, reiniciar procesos congelados si algo salió mal, etc.

Justo después de lanzar nuestros trabajadores, puedes consultarlo a través del menú «Builds» -> «Workers»

Buildbot en los ejemplos

Después de que se realice el primer proceso de compilación (es decir, cambios en el repositorio de Git), aparecerá el estado de los procesos en la primera página.

Buildbot en los ejemplos

Si haces clic en la línea correspondiente, se abrirá una página con el estado actual de este proceso, donde podrás ver qué está sucediendo, qué errores hay, etc.

Buildbot en los ejemplos

Puedes encontrar la configuración completa del maestro aquí

import os, re
from buildbot.plugins import steps, util, schedulers, worker, changes, reporters

c= BuildmasterConfig ={}

c['workers'] = [ worker.Worker('CentOS', '123456'), worker.Worker('Debian-10', '123456'), worker.Worker('Windows-10', '123456')]
c['protocols'] = {'pb': {'port': 4000}} 

c['change_source'] = []
c['change_source'].append(changes.GitPoller(
    repourl = 'https://bitbucket.org/svk28/rac-gui.git',
    project = 'Rac-GUI',
    branches = True,
    pollInterval = 600
    ))

# Servicio para iniciar la construcción
c['schedulers'] = []
c['schedulers'].append(schedulers.SingleBranchScheduler(
    name="Rac-GUI-schedulers",
    change_filter=util.ChangeFilter(branch='master'),
    builderNames=["Rac-GUI-RPM-builder", "Rac-GUI-DEB-builder", "Rac-GUI-WIN-builder"],
    properties = {'owner': 'admin'}
    ))
@util.renderer
def get_name_version_release(props):
    prog_name = "rac-gui"
    prog_version = "1.0.3"
    prog_release = "3"

    return {
    "prog_name": prog_name
    #"prog_version": prog_version,
    #"prog_release": prog_release
}

rac_gui_build_RPM = util.BuildFactory()

rac_gui_build_RPM.addStep(steps.Git(
    repourl = 'https://bitbucket.org/svk28/rac-gui.git',
    workdir = 'rac-gui',
    haltOnFailure = True,
    submodules = True,
    mode='full',
    progress = True)
    )

# Añadimos la definición de la versión desde el archivo principal
rac_gui_build_RPM.addStep(
    steps.SetPropertyFromCommand(
    command="grep version ..\/rac-gui\/rac_gui.tcl | grep -oE 'b[0-9]{1,2}.[0-9]{1,2}.[0-9]{1,2}b'", property="version"
    )
)

# Añadimos la definición del lanzamiento desde el archivo principal
rac_gui_build_RPM.addStep(
    steps.SetPropertyFromCommand(
    command="grep release ..\/rac-gui\/rac_gui.tcl | grep -oE 'b[0-9]{1,3}b'", property="release"
    )
)

rac_gui_build_RPM.addStep(steps.ShellCommand(
    command=["tar", "czf", util.Interpolate("\/home\/buildbot\/rpmbuild\/SOURCES\/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz"), "..\/rac-gui"]))

rac_gui_build_RPM.addStep(steps.ShellCommand(
    command=["cp", "..\/rac-gui\/rac_gui.spec", "\/home\/buildbot\/rpmbuild\/SPECS\/rac_gui.spec"]))

rac_gui_build_RPM.addStep(steps.ShellCommand(
    command=["sed", "-i", util.Interpolate("s\/.*Version:.*\/Version:t%(prop:version)s\/"), "\/home\/buildbot\/rpmbuild\/SPECS\/rac_gui.spec"]))

rac_gui_build_RPM.addStep(steps.ShellCommand(
    command=["sed", "-i", util.Interpolate("s\/.*Release:.*\/Release:t%(prop:release)s\/"), "\/home\/buildbot\/rpmbuild\/SPECS\/rac_gui.spec"]))

rac_gui_build_RPM.addStep(steps.RpmBuild(
    specfile="\/home\/buildbot\/rpmbuild\/SPECS\/rac_gui.spec",
    dist='.el5',
    topdir='\/home\/buildbot\/rpmbuild',
    builddir='\/home\/buildbot\/rpmbuild\/build',
    rpmdir='\/home\/buildbot\/rpmbuild\/RPMS',
    sourcedir='\/home\/buildbot\/rpmbuild\/SOURCES'
    ))

# Copiamos el archivo al maestro
rac_gui_build_RPM.addStep(
    steps.FileUpload(
    workersrc=util.Interpolate("\/home\/buildbot\/rpmbuild\/RPMS\/noarch\/rac-gui-%(prop:version)s-%(prop:release)s.noarch.rpm"),
        masterdest=util.Interpolate("\/home\/buildbot\/builds\/rac-gui-%(prop:version)s-%(prop:release)s.noarch.rpm")
    )
)

rac_gui_build_RPM.addStep(
    steps.FileUpload(
    workersrc=util.Interpolate("\/home\/buildbot\/rpmbuild\/SOURCES\/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz"),
        masterdest=util.Interpolate("\/home\/buildbot\/builds\/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz")
    )
)

rac_gui_build_RPM.addStep(
    steps.MasterShellCommand(
    command=["\/usr\/local\/bin\/deploy-ftp.tcl",
     util.Interpolate("--local-file=\/home\/buildbot\/builds\/rac-gui-%(prop:version)s-%(prop:release)s.noarch.rpm"),
     util.Interpolate("--remote-file=uploads\/rac-gui\/rac-gui-%(prop:version)s-%(prop:release)s.noarch.rpm")]
    )
)

rac_gui_build_RPM.addStep(
    steps.MasterShellCommand(
    command=["\/usr\/local\/bin\/deploy-ftp.tcl",
     util.Interpolate("--local-file=\/home\/buildbot\/builds\/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz"),
     util.Interpolate("--remote-file=uploads\/rac-gui\/rac-gui-%(prop:version)s-%(prop:release)s.tar.gz")]
    )
)
####################################
##      DEB 
####################################

rac_gui_build_DEB = util.BuildFactory()

rac_gui_build_DEB.addStep(steps.Git(
    repourl = 'https://bitbucket.org/svk28/rac-gui.git',
    haltOnFailure = True,
    submodules = True,
    mode='full',
    workdir='build',
    progress = True)
    )

# Añadimos la definición de la versión desde el archivo principal
rac_gui_build_DEB.addStep(
    steps.SetPropertyFromCommand(
    command="grep version rac_gui.tcl | grep -oE 'b[0-9]{1,2}.[0-9]{1,2}.[0-9]{1,2}b'", property="version"
    )
)

# Añadimos la definición del lanzamiento desde el archivo principal
rac_gui_build_DEB.addStep(
    steps.SetPropertyFromCommand(
    command="grep release rac_gui.tcl | grep -oE 'b[0-9]{1,3}b'", property="release"
    )
)

# Renombramos el archivo ejecutable
rac_gui_build_DEB.addStep(steps.ShellCommand(
    command=["mv", "rac_gui.tcl", "racgui"]))

# Cambiamos las rutas a las bibliotecas
rac_gui_build_DEB.addStep(steps.ShellCommand(
    command=["sed", "-i", "s+^set dir(lib)+set dir(lib) \/usr\/share\/rac-gui\/lib ;#+g", "racgui"]))

# Cambiamos las rutas a los archivos
rac_gui_build_DEB.addStep(steps.ShellCommand(
    command=["sed", "-i", "s+[pwd]+\/usr\/share\/rac-gui+g", "racgui"]))

# Archivamos los fuentes
rac_gui_build_DEB.addStep(steps.ShellCommand(
    command=["tar", "czf", util.Interpolate("..\/rac-gui_%(prop:version)s.orig.tar.gz"), "."]))

# Construimos el paquete
rac_gui_build_DEB.addStep(steps.ShellCommand(
    command=["dpkg-buildpackage"]))

# Copiamos el archivo al maestro
rac_gui_build_DEB.addStep(
    steps.FileUpload(
    workersrc=util.Interpolate("..\/rac-gui_%(prop:version)s-%(prop:release)s_amd64.deb"),
        masterdest=util.Interpolate("\/home\/buildbot\/builds\/rac-gui_%(prop:version)s-%(prop:release)s_amd64.deb")
    )
)

rac_gui_build_DEB.addStep(
    steps.MasterShellCommand(
    command=["\/usr\/local\/bin\/deploy-ftp.tcl",
     util.Interpolate("--local-file=\/home\/buildbot\/builds\/rac-gui_%(prop:version)s-%(prop:release)s_amd64.deb"),
     util.Interpolate("--remote-file=uploads\/rac-gui\/rac-gui_%(prop:version)s-%(prop:release)s_amd64.deb")]
    )
)
############################################
#       WIN
############################################

rac_gui_build_WIN = util.BuildFactory()

rac_gui_build_WIN.addStep(steps.Git(
    repourl = 'https://bitbucket.org/svk28/rac-gui.git',
    haltOnFailure = True,
    submodules = True,
    mode='full',
    workdir='build',
    progress = True)
    )

# Añadimos la definición de la versión desde el archivo principal
rac_gui_build_WIN.addStep(
    steps.SetPropertyFromCommand(
    command="powershell -command "((Get-Content .rac_gui.tcl | Select-String -Pattern 'version:') -split 's')[2]",
    property="version"
    )
)

# Añadimos la definición del lanzamiento desde el archivo principal
rac_gui_build_WIN.addStep(
    steps.SetPropertyFromCommand(
    command="powershell -command "((Get-Content .rac_gui.tcl | Select-String -Pattern 'release:') -split 's')[2]",
    property="release"
    )
)

# Creamos el archivo ejecutable
rac_gui_build_WIN.addStep(steps.ShellCommand(
    command=["c:binfreewrap.exe", "rac_gui.tcl"]))

# Empacamos lo que se obtuvo
rac_gui_build_WIN.addStep(steps.ShellCommand(
    command=["c:Program Files7-zip7z.exe", "a", "-r", util.Interpolate("..rac-gui_%(prop:version)s-%(prop:release)s.win.zip"), "..build"]))

# Copiamos al maestro
rac_gui_build_WIN.addStep(
    steps.FileUpload(
    workersrc=util.Interpolate("..rac-gui_%(prop:version)s-%(prop:release)s.win.zip"),
        masterdest=util.Interpolate("\/home\/buildbot\/builds\/rac-gui_%(prop:version)s-%(prop:release)s.win.zip")
    )
)

# Copiamos el archivo al hosting
rac_gui_build_WIN.addStep(
    steps.MasterShellCommand(
    command=["\/usr\/local\/bin\/deploy-ftp.tcl",
     util.Interpolate("--local-file=\/home\/buildbot\/builds\/rac-gui_%(prop:version)s-%(prop:release)s.win.zip"),
     util.Interpolate("--remote-file=uploads\/rac-gui\/rac-gui_%(prop:version)s-%(prop:release)s.win.zip")]
    )
)

c['builders'] = [
        util.BuilderConfig(name="Rac-GUI-RPM-builder", workername='CentOS', factory=rac_gui_build_RPM),
        util.BuilderConfig(name="Rac-GUI-DEB-builder", workername='Debian-10', factory=rac_gui_build_DEB),
        util.BuilderConfig(name="Rac-GUI-WIN-builder", workername='Windows-10', factory=rac_gui_build_WIN),
]

c['services'] = []

template=u'''
<h4>Estado de construcción: {{ summary }}</h4>
<p> Trabajador utilizado: {{ workername }}</p>
{% for step in build['steps'] %}
<p> {{ step['name'] }}: {{ step['result'] }}</p>
{% endfor %}
<p><b> -- The Buildbot</b></p>
'''

mailNotifier = reporters.MailNotifier(fromaddr="112@icvibor.ru",
          sendToInterestedUsers=False,
          mode=('all'),
          extraRecipients=["my@domain.local"],
          relayhost="mail.domain.local",
          smtpPort=587,
          smtpUser="buildbot@domain.local",
          smtpPassword="**********",
          messageFormatter=reporters.MessageFormatter(
                                 template=template, template_type='html',
                                 wantProperties=True, wantSteps=True))

c['services'].append(mailNotifier)

c['title'] = "El proceso de construcción"
c['titleURL'] = "http://vm-srv-build1:80/"

c['buildbotURL'] = "http://vm-srv-build1/"

c['www'] = dict(port=80,
                plugins=dict(waterfall_view={}, console_view={}, grid_view={}))
c['db'] = {
    'db_url' : "sqlite:////state.sqlite"
}

Materiales

Para preparar el artículo se utilizaron los siguientes materiales:

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster