Buildbot in den Beispielen

Ich musste den Prozess zur Erstellung und Lieferung von Softwarepaketen von einem Git-Repository auf die Website einrichten. Und nachdem ich vor nicht allzu langer Zeit einen Artikel auf Habr über Buildbot gesehen habe (Link am Ende), beschloss ich, es auszuprobieren und anzuwenden.

Da Buildbot ein verteiltes System ist, macht es Sinn, für jede Architektur und jedes Betriebssystem einen eigenen Build-Host einzurichten. In unserem Fall werden dies LXC-Container (für Linux) und QEMU (für Windows) sein:

  • vm-srv-build1 – CentOS 7, hier wird der Buildbot-Master (master) und einer der Worker sein
  • vm-srv-build2 – Debian 10, zum Erstellen von DEB-Paketen
  • vm-srv-build3 – Windows 10, zum Erstellen, Sie wissen schon, wofür

Wir werden Folgendes erstellen Rac GUI – das grafische Frontend für 1C rac zur Verwaltung des Serverclusters. Unter Linux werden die Standardwerkzeuge für jedes Betriebssystem verwendet, um die EXE-Datei unter Windows aus einem TCL-Skript zu erstellen, wird freewrap.

Installation von

GNU/Linux

Es gibt genügend Informationen zur Installation und Dokumentation online 1,2. Und sie verursacht auch keine besonderen Probleme:
Für den Master:

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

Für die "Worker" reicht das:

pip3 install buildbot-worker

Selbstverständlich ist es besser, Pakete für jedes Betriebssystem zu erstellen, doch das fällt nicht in den Rahmen dieses Artikels. Ich werde auch auf die Einrichtung der Container verzichten und nur anmerken, dass ich ProxMox VE verwende. Außerdem müssen für den Aufbau erforderliche Pakete für jedes Betriebssystem installiert werden (für CentOS: rpmdevtools usw.; für Debian: build-essential, dh-make, pbuilder usw.).

Die Projekte und die Buildbot-Dienste werden unter dem Namen eines unprivilegierten Benutzers gestartet. Daher muss dieser auf allen Hosts, die am Prozess teilnehmen, erstellt werden:


adduser buildbot

Nun konfigurieren wir den automatischen Start der Dienste entsprechend auf jedem der Hosts (Container):

Systemd-Einheit zum Starten des Masters:

touch /etc/systemd/buildbot-master.service 

[Unit]
Description=BuildBot-Masterdienst
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

und "Arbeiter"

touch /etc/systemd/buildbot-worker.service 

[Unit]
Description=BuildBot-Arbeiterdienst
After=network.target

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

[Install]
WantedBy=buildbot-master.service

Da alle Skripte (in unserem Fall) in /usr/local/ liegen, sollte der Pfad zu ihnen in die Umgebungsvariablen aufgenommen werden:

nano /root/.bash_profile

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

Danach können Sie eine Verzeichnisstruktur für die "Arbeiter" (auf allen Hosts) erstellen, indem Sie sich als Benutzer buildbot anmelden und die folgenden Befehle ausführen:

Auf dem ersten 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

Auf dem zweiten 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

Auf den Worker-Hosts kann der Dienst buildbot-worker gestartet werden

systemctl start buildbot-worker

MS Windows

Für den "Arbeiter" zur Erstellung unter Windows wird eine virtuelle Maschine mit der neuesten Version von Win10 verwendet.
Für den Betrieb wird Folgendes benötigt:

Nachdem alles oben Genannte installiert ist, können wir auch den buildbot selbst installieren:

pip install buildbot-worker

Erstellen wir das Arbeitsverzeichnis

md c:worker

Und starten wir

buildbot-worker start c:worker

Wenn alles funktioniert (siehe Protokoll c:workertwistd.log), können wir unseren "Arbeiter" registrieren, indem wir den Punkt mit dem Arbeitsverzeichnis zur Registrierung hinzufügen (Befehle werden in PowerShell ausgeführt, die als Administrator gestartet wurde):

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

Und Sie können den Dienst starten

Start-Service buildbot

Das wäre alles zu den „Arbeitern“. Diese werden nicht weiter bearbeitet, die gesamte Steuerung erfolgt vom Master.

Konfiguration des Masters

Zunächst erstellen wir die Infrastruktur für den Master (auf dem Haupt-Host). Dazu melden wir uns als Benutzer buildbot an und führen die folgenden Befehle aus:

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

Für die fertigen Pakete erstellen wir ein Verzeichnis builds

mkdir /home/buildbot/builds

Im Verzeichnis /home/buildbot/master/ wurde die Datei master.cfg erstellt. Diese Datei ist ein Python-Script und enthält die Beschreibung aller Mechanismen des Systems, mit dem wir in der Folge arbeiten werden.

nano /home/buildbot/master/master.cfg

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

c = BuildmasterConfig ={}

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

# Angabe an den Master, welches Repository überwacht werden soll
c['change_source'] = []
c['change_source'].append(changes.GitPoller(
       repourl = 'https://bitbucket.org/svk28/rac-gui.git',
       project = 'Rac-GUI',
       branches = True,
       pollInterval = 60
      )
)

# Dienst zum Starten des Builds
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
# RPM-Paketbau
 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)
)

Zur Automatisierung des Builds von Paketen verschiedener Versionen, ohne in den Code der master.cfg-Datei eintauchen zu müssen, wurden im Hauptskript des Programms rac_gui.tcl Zeilen mit der aktuellen Version und dem Release hinzugefügt:

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

Basierend auf diesen Zeilen wird Buildbot die Pakete nummerieren. Zum Abrufen der Daten wird der Konsolenbefehl grep verwendet. In Buildbot können Variablen für die „Arbeiter“ nicht einfach festgelegt werden (jedenfalls habe ich keinen Weg gefunden). Hierfür gibt es Eigenschaften (Property). Das bedeutet, dass wir im Build-Prozess Schritte zur Definition von Version und Release hinzufügen und entsprechend die Eigenschaften version und release festlegen. Eigenschaften können auf verschiedene Weise festgelegt werden, in diesem Fall durch den Aufruf eines Konsolenbefehls:

# Добавим определение версии из основного файла
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"
       )
)

Um die erhaltenen Werte zu verwenden, kann util.Interpolate() aufgerufen werden.

# Запакуем исходники
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"]
       )
)

Es ist zu beachten, dass der Host auch für manuelles Erstellen von Paketen verwendet wird, weshalb der Build über die Standardpfade erfolgt.

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

Für die korrekte Festlegung der Versions- und Release-Nummern wird der Standardbefehl sed verwendet, das heißt, der Befehl ersetzt die Werte innerhalb der Spec-Datei durch die erforderlichen.

# меняем версию
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'
      )
)

Das fertige Paket und das Archiv mit den Quellcodes kopieren wir auf den Master. Alternativ können die Dateien auch direkt von der Arbeitsstation in Ihr Repository oder auf die Website kopiert werden.

# Скопируем файл на мастер
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")
       )
)

Wir starten auf dem Master den Kopiervorgang der erstellten Pakete auf das Hosting per FTP. Hierfür wird Skript tcl verwendet.

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")]
        )
)

Damit haben wir die RPM installiert. Jetzt beginnen wir mit der Beschreibung des Algorithmus zur Erstellung des DEB-Pakets. Da die Paketbuild-Prozesse für verschiedene Systeme unabhängig voneinander sind, werden viele Schritte wiederholt.


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)
)

# Fügen wir die Versionsbestimmung aus der Hauptdatei hinzu
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"
       )
)

# Fügen wir die Release-Bestimmung aus der Hauptdatei hinzu
rac_gui_build_DEB.addStep(
       steps.SetPropertyFromCommand(
       command="grep release rac_gui.tcl | grep -oE 'b[0-9]{1,3}b'", property="release"
       )
)

# Benennen wir die ausführbare Datei um
    rac_gui_build_DEB.addStep(steps.ShellCommand(
       command=["mv", "rac_gui.tcl", "racgui"]))

Für das RPM-Paket werden einige der folgenden Schritte vom rpm bei der Erstellung durchgeführt und sind im Spezifikationsdokument beschrieben. Bei Debian müssen wir das hier übernehmen:

# Поменяем пути к библиотекам
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")]
        )
)

Und mit DEB ist es vorbei, jetzt 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)
)

Da es in Windows standardmäßig kein grep und sed gibt (oder doch?), verwenden wir 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),
]

Für die Benachrichtigung über den Status des Build-Prozesses verwenden wir elektronische Post.


c['services'] = []

template=u'''
    <h4>Bau-Status: {{ summary }}</h4>
    <p> Verwendeter Worker: {{ workername }}</p>
    {% for step in build['steps'] %}
    <p> {{ step['name'] }}: {{ step['result'] }}</p>
    {% endfor %}
    <p><b> -- Der Buildbot</b></p>
    '''

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

c['services'].append(mailNotifier)

# Hauptkonfiguration des Masters
c['title'] = "Der Prozess des Bauens"
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"
}

Wir speichern die Datei und können versuchen, den Buildbot-Master-Dienst zu starten:

systemctl restart buildbot-master

Im Log werden wir überprüfen, ob mit der Konfiguration alles in Ordnung ist und alles reibungslos funktioniert. Alle unsere Mitarbeiter sollten sich jetzt anmelden, was im Log »»’/home/buildbot/master/twistd.log»»’ erfreulich mitgeteilt wird:

 2019-07-24 16:50:35+0300 [-] Lade buildbot.tac...
 2019-07-24 16:50:35+0300 [-] Geladen.
 2019-07-24 16:50:35+0300 [-] twistd 19.2.1 (/usr/bin/python3.6 3.6.8) startet.
 2019-07-24 16:50:35+0300 [-] Reaktor-Klasse: twisted.internet.epollreactor.EPollReactor.
 2019-07-24 16:50:35+0300 [-] Starte BuildMaster -- buildbot.version: 2.3.1
 2019-07-24 16:50:35+0300 [-] Lade Konfiguration von '/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 und später] `buildbotNetUsageData` ist nicht konfiguriert und läuft auf Standard.
        Dieses Parameter hilft dem Buildbot-Entwicklungsteam, die Installationsbasis zu verstehen.
        Es werden keine persönlichen Daten gesammelt.
        Nur Informationen zur Version der Installationssoftware und zur Nutzung von Plugins werden gesendet.
        Sie können sich `abmelden`, indem Sie diese Variable auf None setzen.
        Oder sich `anmelden`, um mehr Informationen zu erhalten, indem Sie sie auf "full" setzen.

 2019-07-24 16:50:36+0300 [-] Richte Datenbank mit URL 'sqlite:/state.sqlite' ein
 2019-07-24 16:50:36+0300 [-] Setze Datenbank-Journalmodus auf 'wal'
 2019-07-24 16:50:36+0300 [-] Füge 1 neue Dienste hinzu, entferne 0
 2019-07-24 16:50:36+0300 [-] Füge 1 neue change_sources hinzu, entferne 0
 2019-07-24 16:50:36+0300 [-] gitpoller: benutze Arbeitsverzeichnis '/home/buildbot/master/gitpoller-work'
 2019-07-24 16:50:36+0300 [-] Füge 3 neue Builder hinzu, entferne 0
 2019-07-24 16:50:36+0300 [-] Füge 1 neuen Scheduler hinzu, entferne 0
 2019-07-24 16:50:36+0300 [-] Initialisiere www-Plugin 'waterfall_view'
 2019-07-24 16:50:36+0300 [-] Initialisiere www-Plugin 'console_view'
 2019-07-24 16:50:36+0300 [-] Initialisiere www-Plugin 'grid_view'
 2019-07-24 16:50:36+0300 [-] HINWEIS: www-Plugin 'sitenav' ist installiert, aber nicht konfiguriert
 2019-07-24 16:50:36+0300 [-] Initialisiere www-Plugin 'waterfall_view'
 2019-07-24 16:50:36+0300 [-] Initialisiere www-Plugin 'console_view'
 2019-07-24 16:50:36+0300 [-] Initialisiere www-Plugin 'grid_view'
 2019-07-24 16:50:36+0300 [-] HINWEIS: www-Plugin 'sitenav' ist installiert, aber nicht konfiguriert
 2019-07-24 16:50:36+0300 [-] BuildbotSite startet auf 80
 2019-07-24 16:50:36+0300 [-] Starte Fabrik 
 2019-07-24 16:50:36+0300 [-] Füge 3 neue Arbeiter hinzu, entferne 0
 2019-07-24 16:50:36+0300 [-] PBServerFactory startet auf 4000
 2019-07-24 16:50:36+0300 [-] Starte Fabrik 
 2019-07-24 16:50:37+0300 [-] BuildMaster läuft
 2019-07-24 16:50:37+0300 [-] buildbotNetUsageData: sendet {'installid': 'b6193b126b96689351d2fe95787c5a03fc0879f9', 'versions': {'Python': '3.6.8', 'Buildbot': '2.3.1', 'Twisted': '19.2.1'}, 'plattforms': {'platform': 'Linux-4.15.18-10-pve-x86_64-with-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] Arbeiter 'CentOS' verbindet sich von IPv4Address(type='TCP', host='127.0.0.1', port=37332)
 2019-07-24 16:50:37+0300 [Broker,0,127.0.0.1] Arbeiterinfo von 'CentOS' erhalten
 2019-07-24 16:50:37+0300 [-] Bot verbunden
 2019-07-24 16:50:37+0300 [Broker,0,127.0.0.1] Arbeiter CentOS verbunden mit Rac-GUI-RPM-builder
 2019-07-24 16:50:37+0300 [-] buildbotNetUsageData: buildbot.net meldete: ok
 2019-07-24 16:50:39+0300 [Broker,1,192.168.55.15] Arbeiter 'Windows-10' verbindet sich von IPv4Address(type='TCP', host='192.168.5.145', port=49831)
 2019-07-24 16:50:39+0300 [Broker,1,192.168.55.15] Arbeiterinfo von 'Windows-10' erhalten
 2019-07-24 16:50:40+0300 [-] Bot verbunden
 2019-07-24 16:50:40+0300 [Broker,1,192.168.55.15] Arbeiter Windows-10 verbunden mit Rac-GUI-WIN-builder
 2019-07-24 16:50:41+0300 [Broker,2,192.168.55.99] Arbeiter 'Debian-10' verbindet sich von IPv4Address(type='TCP', host='192.168.5.9', port=44430)
 2019-07-24 16:50:41+0300 [Broker,2,192.168.55.99] Arbeiterinfo von 'Debian-10' erhalten
 2019-07-24 16:50:41+0300 [-] Bot verbunden
 2019-07-24 16:50:41+0300 [Broker,2,192.168.55.99] Arbeiter Debian-10 verbunden mit Rac-GUI-DEB-builder

Der Einstellungsprozess ist hiermit abgeschlossen. Der aktuelle Status kann über die Web-Oberfläche eingesehen werden. Dort können auch Fehlerprotokolle angesehen, festgefahrene Prozesse neu gestartet und vieles mehr gemacht werden.

Sofort nach dem Start unserer Worker kann dieser über das Menü „Builds“ -> „Workers“ eingesehen werden.

Buildbot in den Beispielen

Nach dem ersten Build-Prozess (d.h. nach Änderungen im Git-Repository) wird der Status der Prozesse auf der ersten Seite angezeigt.

Buildbot in den Beispielen

Wenn Sie mit der Maus auf die gewünschte Zeile klicken, öffnet sich eine Seite mit dem aktuellen Status dieses Prozesses, auf der sichtbar ist, was gerade passiert, welche Fehler aufgetreten sind usw.

Buildbot in den Beispielen

Die vollständige Konfiguration des Masters kann hier heruntergeladen werden.

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
    ))

# Build-Start-Dienst
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)
    )

# Fügen wir die Versionsdefinition aus der Hauptdatei hinzu
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"
    )
)

# Fügen wir die Release-Definition aus der Hauptdatei hinzu
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'
    ))

# Kopiere die Datei auf den Master
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)
    )

# Fügen wir die Versionsdefinition aus der Hauptdatei hinzu
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"
    )
)

# Fügen wir die Release-Definition aus der Hauptdatei hinzu
rac_gui_build_DEB.addStep(
    steps.SetPropertyFromCommand(
    command="grep release rac_gui.tcl | grep -oE 'b[0-9]{1,3}b'", property="release"
    )
)

# Benennen wir die ausführbare Datei um
rac_gui_build_DEB.addStep(steps.ShellCommand(
    command=["mv", "rac_gui.tcl", "racgui"]))

# Ändern wir die Bibliothekspfade
rac_gui_build_DEB.addStep(steps.ShellCommand(
    command=["sed", "-i", "s+^set dir(lib)+set dir(lib) /usr/share/rac-gui/lib ;#+g", "racgui"]))

# Ändern wir die Dateipfade
rac_gui_build_DEB.addStep(steps.ShellCommand(
    command=["sed", "-i", "s+[pwd]+/usr/share/rac-gui+g", "racgui"]))

# Archivieren wir die Quellen
rac_gui_build_DEB.addStep(steps.ShellCommand(
    command=["tar", "czf", util.Interpolate("../rac-gui_%(prop:version)s.orig.tar.gz"), "."]))

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

# Kopiere die Datei auf den Master
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)
    )

# Fügen wir die Versionsdefinition aus der Hauptdatei hinzu
rac_gui_build_WIN.addStep(
    steps.SetPropertyFromCommand(
    command="powershell -command "((Get-Content .rac_gui.tcl | Select-String -Pattern 'version:') -split 's')[2]",
    property="version"
    )
)

# Fügen wir die Release-Definition aus der Hauptdatei hinzu
rac_gui_build_WIN.addStep(
    steps.SetPropertyFromCommand(
    command="powershell -command "((Get-Content .rac_gui.tcl | Select-String -Pattern 'release:') -split 's')[2]",
    property="release"
    )
)

# Erstellen wir die ausführbare Datei
rac_gui_build_WIN.addStep(steps.ShellCommand(
    command=["c:binfreewrap.exe", "rac_gui.tcl"]))

# Packen wir, was wir haben
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"]))

# Kopieren wir auf den Master
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")
    )
)

# Kopiere die Datei auf das 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>Bau-Status: {{ summary }}</h4>
<p> Verwendeter Worker: {{ workername }}</p>
{% for step in build['steps'] %}
<p> {{ step['name'] }}: {{ step['result'] }}</p>
{% endfor %}
<p><b> -- Der 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'] = "Der Prozess des Aufbaus"
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"
}

Materialien

Zur Vorbereitung des Artikels wurden folgende Materialien verwendet:

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster