Buildbot: eine Geschichte mit Beispielen eines anderen kontinuierlichen Integrationssystems


(Bild von der offiziellen Seite )

Buildbot ist, wie der Name schon sagt, ein kontinuierliches Integrationssystem (ci). Es gab bereits mehrere Artikel über ihn auf dem Habré, aber aus meiner Sicht sind die Vorteile dieses Werkzeugs nicht sehr klar. Außerdem gibt es fast keine Beispiele, was es schwierig macht, die volle Leistungsfähigkeit des Programms zu erkennen. In meinem Artikel werde ich versuchen, diese Mängel auszugleichen, über das interne Gerät Buildbot'a zu sprechen und Beispiele für einige nicht standardmäßige Skripte zu geben.

Gemeinsame Wörter


Derzeit gibt es eine große Anzahl kontinuierlicher Integrationssysteme, und wenn es um eines davon geht, stellen sich ganz logische Fragen im Sinne von "Warum wird es benötigt, wenn Sie bereits einen <Programmname> haben und jeder ihn verwendet?" Ich werde versuchen, eine solche Frage zu Buildbot zu beantworten. Einige der Informationen werden mit vorhandenen Artikeln dupliziert, andere sind in der offiziellen Dokumentation beschrieben, dies ist jedoch für die Konsistenz der Erzählung erforderlich.

Der Hauptunterschied zu anderen kontinuierlichen Integrationssystemen besteht darin, dass Buildbot ein Python-Framework zum Schreiben von ci ist und keine sofort einsatzbereite Lösung. Dies bedeutet, dass Sie zum Verbinden eines Projekts mit Buildbot zunächst ein separates Python-Programm mit dem Buildbot-Framework schreiben müssen, das die für Ihr Projekt erforderliche kontinuierliche Integrationsfunktion implementiert. Dieser Ansatz bietet enorme Flexibilität und ermöglicht es Ihnen, knifflige Testszenarien zu implementieren, die aufgrund von Architekturbeschränkungen für Out-of-Box-Lösungen nicht möglich sind.

Außerdem ist Buildbot kein Dienst, und daher müssen Sie ihn ehrlich in Ihrer Infrastruktur bereitstellen. Hier stelle ich fest, dass das Framework den Ressourcen des Systems sehr treu ist. Dies ist sicherlich nicht C oder C ++, aber Python gewinnt gegen seine Java-Konkurrenten. Hier zum Beispiel der Vergleich des Speicherverbrauchs mit GoCD (und ja, trotz des Namens ist dies ein Java-System):

Buildbot:



GoCD:



Das Bereitstellen und Schreiben eines separaten Testprogramms selbst kann Sie bei dem Gedanken an die Ersteinrichtung traurig machen. Die Skripterstellung wird jedoch durch die schiere Anzahl der integrierten Klassen erheblich vereinfacht. Diese Klassen decken viele Standardoperationen ab, unabhängig davon, ob Änderungen vom Github-Repository abgerufen oder das Projekt mit CMake erstellt werden. Daher sind Standardskripte für kleine Projekte für einige Travis-CI nicht komplizierter als YML-Dateien. Ich werde nicht über die Bereitstellung schreiben, dies wird in bestehenden Artikeln ausführlich behandelt und es gibt dort auch nichts Kompliziertes.

Als nächstes von Buildbot stelle ich fest, dass die Testlogik standardmäßig auf der Seite des ci-Servers implementiert ist. Dies steht im Widerspruch zu dem mittlerweile beliebten Ansatz „Pipeline als Code“, bei dem die Testlogik in einer Datei (wie .travis.yml) beschrieben wird, die sich zusammen mit dem Projektquellcode im Repository befindet, und der ci-Server diese Datei nur liest und ausführt was es sagt. Auch dies ist nur das Standardverhalten. Mit den Funktionen des Buildbot-Frameworks können Sie den beschriebenen Ansatz implementieren, indem Sie das Testskript im Repository speichern. Es gibt sogar eine fertige Lösung - bb-travis , die versucht, das Beste aus Buildbot und travis-ci herauszuholen. Darüber hinaus werde ich später in diesem Artikel beschreiben, wie Sie etwas Ähnliches wie dieses Verhalten selbst implementieren können.

Buildbot sammelt standardmäßig jedes Commit beim Push. Es mag wie ein kleines unnötiges Merkmal erscheinen, aber für mich ist es im Gegenteil einer der Hauptvorteile geworden. Viele gängige Lösungen (travis-ci, gitlab-ci) bieten überhaupt keine solche Möglichkeit und arbeiten nur mit dem letzten Commit in der Branche. Stellen Sie sich vor, dass Sie während der Entwicklung häufig Commits auswählen müssen. Es wird unangenehm sein, ein nicht funktionierendes Commit durchzuführen, das vom Build-System nicht überprüft wurde, da es zusammen mit einer Reihe von Commits von oben gestartet wurde. Natürlich können Sie in Buildbot nur das letzte Commit erstellen, und dies erfolgt durch Festlegen nur eines Parameters.

Das Framework verfügt über eine recht gute Dokumentation, die alles detailliert beschreibt, von der allgemeinen Architektur bis zu den Richtlinien für die Erweiterung der integrierten Klassen. Selbst mit einer solchen Dokumentation müssen Sie möglicherweise einige Dinge im Quellcode betrachten. Es ist unter der GPL v2-Lizenz vollständig geöffnet und leicht zu lesen. Von den Minuspunkten - die Dokumentation ist nur in englischer Sprache verfügbar, in russischer Sprache gibt es nur sehr wenige Informationen im Netzwerk. Das Tool ist gestern nicht erschienen, mit seiner Hilfe werden Python , Wireshark , LLVM und viele andere bekannte Projekte zusammengestellt. Updates werden veröffentlicht, das Projekt wird von vielen Entwicklern unterstützt, sodass wir über Zuverlässigkeit und Stabilität sprechen können.


(Python Buildbot Homepage)

Theormin


Dieser Teil ist im Wesentlichen eine freie Übersetzung des Kapitels der offiziellen Dokumentation zur Architektur des Frameworks. Es zeigt die gesamte Aktionskette vom Empfang von Änderungen durch das ci-System bis zum Senden von Benachrichtigungen über das Ergebnis an Benutzer. Sie haben also Änderungen am Quellcode des Projekts vorgenommen und diese an das Remote-Repository gesendet. Was als nächstes passiert, ist im Bild schematisch dargestellt:


(Bild aus offizieller Dokumentation )

Zunächst sollte Buildbot irgendwie herausfinden, dass Änderungen im Repository vorgenommen wurden. Es gibt zwei Hauptmethoden - Webhooks und Umfragen, obwohl niemand verbietet, sich etwas Anspruchsvolleres auszudenken. Im ersten Fall sind in Buildbot die BaseHookHandler-Nachkommenklassen dafür verantwortlich. Es gibt viele vorgefertigte Lösungen, zum Beispiel GitHubHandler oder GitoriusHandler . Die Schlüsselmethode in diesen Klassen ist getChanges () . Die Logik ist äußerst einfach: Sie muss die HTTP-Anforderung in eine Liste von Änderungsobjekten konvertieren.

Für den zweiten Fall benötigen Sie PollingChangeSource- Nachkommenklassen. Auch hier gibt es vorgefertigte Lösungen wie GitPoller oder HgPoller . Die Schlüsselmethode ist poll () . Es wird mit einer bestimmten Häufigkeit aufgerufen und muss irgendwie eine Liste der Änderungen im Repository erstellen. Im Fall eines Git kann dies ein Aufruf zum Abrufen von Git und ein Vergleich mit dem zuvor gespeicherten Status sein. Wenn die integrierten Funktionen nicht ausreichen, erstellen Sie einfach Ihre eigene Vererbungsklasse und überladen Sie die Methode. Ein Beispiel für die Verwendung von Polling:

c['change_source'] = [changes.GitPoller( repourl = 'git@git.example.com:project', project = 'My Project', branches = True, #      pollInterval = 60 )] 

Webhook ist noch einfacher zu bedienen, die Hauptsache ist, nicht zu vergessen, es auf der Git-Server-Seite zu konfigurieren. Dies ist nur eine Zeile in der Konfigurationsdatei:

 c['www']['change_hook_dialects'] = { 'github': {} } 

Im nächsten Schritt werden die Änderungsobjekte in die Scheduler-Objekte ( Scheduler ) eingegeben. Beispiele für integrierte Scheduler: AnyBranchScheduler , NightlyScheduler , ForceScheduler usw. Jeder Scheduler empfängt alle Änderungsobjekte als Eingabe, wählt jedoch nur diejenigen aus, die den Filter bestehen. Der Filter wird über das Argument change_filter an den Scheduler im Konstruktor übergeben . Am Ausgang erstellen die Planer Build-Anforderungen. Der Scheduler wählt die Builder basierend auf dem Builder-Argument aus.

Einige Planer haben ein kniffliges Argument namens treeStableTimer . Dies funktioniert wie folgt: Wenn eine Änderung eingeht, erstellt der Scheduler nicht sofort eine neue Build-Anforderung, sondern startet einen Timer. Wenn neue Änderungen eintreffen und der Timer nicht abgelaufen ist, wird die alte Änderung durch eine neue ersetzt und der Timer aktualisiert. Wenn der Timer endet, erstellt der Scheduler nur eine Build-Anforderung aus der zuletzt gespeicherten Änderung.

Somit ist die Logik implementiert, nur das letzte Commit beim Push zusammenzusetzen. Beispiel für die Scheduler-Konfiguration:

 c['schedulers'] = [schedulers.AnyBranchScheduler( name = 'My Scheduler', treeStableTimer = None, change_filter = util.ChangeFilter(project = 'My Project'), builderNames = ['My Builder'] )] 

Build-Anforderungen, so seltsam sie auch klingen mögen, gehen an die Eingabe der Builder. Die Aufgabe des Sammlers besteht darin, die Assembly auf einem zugänglichen „Worker“ auszuführen. Worker ist eine Build-Umgebung wie Stretch64 oder Ubuntu1804x64. Die Liste der Arbeiter wird durch das Arbeiterargument geleitet . Alle Arbeiter in der Liste sollten gleich sein (d. H. Die Namen sind natürlich unterschiedlich, aber die Umgebung im Inneren ist die gleiche), da der Sammler frei ist, einen der verfügbaren auszuwählen. Das Festlegen mehrerer Werte dient hier dazu, die Last auszugleichen und nicht in unterschiedlichen Umgebungen zu bauen. Mit dem Argument Faktor y erhält der Kollektor eine Folge von Schritten zum Erstellen des Projekts. Ich werde weiter unten ausführlich darüber schreiben.

Beispiel für die Konfiguration des Kollektors

 c['builders'] = [util.BuilderConfig( name = 'My Builder', workernames = ['stretch32'], factory = factory )] 

Das Projekt ist also fertig. Der letzte Schritt von Buildbot besteht darin, den Build zu benachrichtigen. Reporterklassen sind dafür verantwortlich. Ein klassisches Beispiel ist die MailNotifier- Klasse, die eine E-Mail mit Build-Ergebnissen sendet. Beispiel für eine MailNotifier- Verbindung:

 c['services'] = [reporters.MailNotifier( fromaddr = 'buildbot@example.com', relayhost = 'mail.example.com', smtpPort = 25, extraRecipients = ['devel@example.com'], sendToInterestedUsers = False )] 

Nun, es ist Zeit, zu vollwertigen Beispielen überzugehen. Ich stelle fest, dass Buildbot selbst mit dem Twisted-Framework geschrieben wurde und daher die Kenntnis des Buildbot-Skripts das Schreiben und Verstehen von Buildbot-Skripten erheblich erleichtert. Wir werden einen Prügelknaben für ein Projekt namens Pet Project haben. Lassen Sie es in C ++ schreiben, mit CMake zusammenstellen, und der Quellcode liegt im Git-Repository. Wir waren nicht zu faul und haben Tests für ihn geschrieben, die vom ctest-Team durchgeführt werden. Zuletzt haben wir diesen Artikel gelesen und festgestellt, dass wir das frisch gewonnene Wissen auf unser Projekt anwenden möchten.

Beispiel eins: damit es funktioniert


Eigentlich ist die Konfigurationsdatei:

100 Zeilen Python-Code
 from buildbot.plugins import * # shortcut c = BuildmasterConfig = {} # create workers c['workers'] = [worker.Worker('stretch32', 'example_password')] # general settings c['title'] = 'Buildbot: test' c['titleURL'] = 'https://buildbot.example.com/' c['buildbotURL'] = 'https://buildbot.example.com/' # setup database c['db'] = { 'db_url': 'sqlite:///state.sqlite' } # port to communicate with workers c['protocols'] = { 'pb': { 'port': 9989 } } # make buildbot developers a little bit happier c['buildbotNetUsageData'] = 'basic' # webserver setup c['www'] = dict(plugins = dict(waterfall_view={}, console_view={}, grid_view={})) c['www']['authz'] = util.Authz( allowRules = [util.AnyEndpointMatcher(role = 'admins')], roleMatchers = [util.RolesFromUsername(roles = ['admins'], usernames = ['root'])] ) c['www']['auth'] = util.UserPasswordAuth([('root', 'root_password')]) # mail notification c['services'] = [reporters.MailNotifier( fromaddr = 'buildbot@example.com', relayhost = 'mail.example.com', smtpPort = 25, extraRecipients = ['devel@example.com'], sendToInterestedUsers = False )] c['change_source'] = [changes.GitPoller( repourl = 'git@git.example.com:pet-project', project = 'Pet Project', branches = True, pollInterval = 60 )] c['schedulers'] = [schedulers.AnyBranchScheduler( name = 'Pet Project Scheduler', treeStableTimer = None, change_filter = util.ChangeFilter(project = 'Pet Project'), builderNames = ['Pet Project Builder'] )] factory = util.BuildFactory() factory.addStep(steps.Git( repourl = util.Property('repository'), workdir = 'sources', haltOnFailure = True, submodules = True, progress = True) ) factory.addStep(steps.ShellSequence( name = 'create builddir', haltOnFailure = True, hideStepIf = lambda results, s: results == util.SUCCESS, commands = [ util.ShellArg(command = ['rm', '-rf', 'build']), util.ShellArg(command = ['mkdir', 'build']) ]) ) factory.addStep(steps.CMake( workdir = 'build', path = '../sources', haltOnFailure = True) ) factory.addStep(steps.Compile( name = 'build project', workdir = 'build', haltOnFailure = True, warnOnWarnings = True, command = ['make']) ) factory.addStep(steps.ShellCommand( name = 'run tests', workdir = 'build', haltOnFailure = True, command = ['ctest']) ) c['builders'] = [util.BuilderConfig( name = 'Pet Project Builder', workernames = ['stretch32'], factory = factory )] 


Durch das Schreiben dieser Zeilen erhalten wir eine automatische Assemblierung beim Pushing in das Repository, ein schönes Webface, E-Mail-Benachrichtigungen und andere Attribute eines sich selbst respektierenden ci. Das meiste davon sollte klar sein: Die Einstellungen der Scheduler, Collectors und anderer Objekte werden ähnlich wie in den zuvor angegebenen Beispielen vorgenommen. Der Wert der meisten Parameter ist intuitiv. Im Detail werde ich mich nur auf die Schaffung einer Fabrik konzentrieren, was ich früher versprochen habe.

Die Factory besteht aus Build-Schritten , die Buildbot für das Projekt ausführen muss. Wie bei anderen Klassen gibt es viele vorgefertigte Lösungen. Unsere Fabrik besteht aus fünf Schritten. In der Regel besteht der erste Schritt darin, den aktuellen Status des Repositorys abzurufen. Hier machen wir keine Ausnahme. Dazu verwenden wir die Standard- Git- Klasse:

Erster Schritt
 factory = util.BuildFactory() factory.addStep(steps.Git( repourl = util.Property('repository'), workdir = 'sources', haltOnFailure = True, submodules = True, progress = True) ) 


Als nächstes müssen wir ein Verzeichnis erstellen, in dem das Projekt zusammengestellt wird - wir werden einen vollständigen Build aus dem Quellcode erstellen. Zuvor müssen Sie daran denken, das Verzeichnis zu löschen, falls es bereits vorhanden ist. Daher müssen wir zwei Befehle ausführen. Die ShellSequence- Klasse hilft uns dabei:

Zweiter Schritt
 factory.addStep(steps.ShellSequence( name = 'create builddir', haltOnFailure = True, hideStepIf = lambda results, s: results == util.SUCCESS, commands = [ util.ShellArg(command = ['rm', '-rf', 'build']), util.ShellArg(command = ['mkdir', 'build']) ]) ) 


Jetzt müssen Sie CMake starten. Dazu ist es logisch, eine von zwei Klassen zu verwenden - ShellCommand oder CMake . Wir werden letzteres verwenden, aber die Unterschiede sind minimal: Es ist ein einfacher Wrapper über die erste Klasse, was es etwas bequemer macht, CMake-spezifische Argumente zu übergeben.

Dritter Schritt
 factory.addStep(steps.CMake( workdir = 'build', path = '../sources', haltOnFailure = True) ) 


Zeit, das Projekt zu kompilieren. Wie im vorherigen Fall können Sie ShellCommand verwenden . Ebenso gibt es die Compile- Klasse, die einen Wrapper über ShellCommand darstellt . Dies ist jedoch ein schwierigerer Wrapper: Die Compile- Klasse überwacht Warnungen während der Kompilierung und zeigt sie genau in einem separaten Protokoll an. Deshalb werden wir die Compile- Klasse verwenden:

Vierter Schritt
 factory.addStep(steps.Compile( name = 'build project', workdir = 'build', haltOnFailure = True, warnOnWarnings = True, command = ['make']) ) 


Führen Sie zum Schluss unsere Tests durch. Hier verwenden wir die zuvor erwähnte ShellCommand- Klasse:

Fünfter Schritt
 factory.addStep(steps.ShellCommand( name = 'run tests', workdir = 'build', haltOnFailure = True, command = ['ctest']) ) 


Beispiel zwei: Pipeline als Code


Hier werde ich zeigen, wie eine Budgetoption zum Speichern der Testlogik zusammen mit dem Projektquellcode und nicht in der ci-Server-Konfigurationsdatei implementiert wird. Fügen Sie dazu die .buildbot- Datei mit dem Code in das Repository ein, in dem jede Zeile aus Wörtern besteht, von denen die erste als Verzeichnis für die Ausführung des Befehls und der Rest als Befehl mit ihren Argumenten interpretiert wird. Für unser Haustierprojekt sieht die .buildbot- Datei folgendermaßen aus:

.Buildbot-Datei mit Befehlen
. rm -rf build
. mkdir build
build cmake ../sources
build make
build ctest


Jetzt müssen wir die Buildbot-Konfigurationsdatei ändern. Um die .buildbot- Datei zu analysieren, müssen wir eine Klasse unseres eigenen Schritts schreiben. In diesem Schritt wird die .buildbot- Datei gelesen. Anschließend wird für jede Zeile der ShellCommand- Schritt mit den erforderlichen Argumenten hinzugefügt . Um Schritte dynamisch hinzuzufügen, verwenden wir die Methode build.addStepsAfterCurrentStep () . Es sieht überhaupt nicht beängstigend aus:

Klasse AnalyseSchritt
 class AnalyseStep(ShellMixin, BuildStep): def __init__(self, workdir, **kwargs): kwargs = self.setupShellMixin(kwargs, prohibitArgs = ['command', 'workdir', 'want_stdout']) BuildStep.__init__(self, **kwargs) self.workdir = workdir @defer.inlineCallbacks def run(self): self.stdio_log = yield self.addLog('stdio') cmd = RemoteShellCommand( command = ['cat', '.buildbot'], workdir = self.workdir, want_stdout = True, want_stderr = True, collectStdout = True ) cmd.useLog(self.stdio_log) yield self.runCommand(cmd) if cmd.didFail(): defer.returnValue(util.FAILURE) results = [] for row in cmd.stdout.splitlines(): lst = row.split() dirname = lst.pop(0) results.append(steps.ShellCommand( name = lst[0], command = lst, workdir = dirname ) ) self.build.addStepsAfterCurrentStep(results) defer.returnValue(util.SUCCESS) 


Dank dieses Ansatzes ist die Fabrik für den Sammler einfacher und vielseitiger geworden:

Factory zum Analysieren der .buildbot-Datei
 factory = util.BuildFactory() factory.addStep(steps.Git( repourl = util.Property('repository'), workdir = 'sources', haltOnFailure = True, submodules = True, progress = True, mode = 'incremental') ) factory.addStep(AnalyseStep( name = 'Analyse .buildbot file', workdir = 'sources', haltOnFailure = True, hideStepIf = lambda results, s: results == util.SUCCESS) ) 


Beispiel drei: Arbeiter als Code


Stellen Sie sich nun vor, dass wir neben dem Projektcode nicht die Reihenfolge der Befehle bestimmen müssen, sondern die Umgebung für die Assembly. In der Tat definieren wir Arbeiter. Die .buildbot- Datei könnte ungefähr so aussehen:

.Buildbot-Umgebungsdatei
{
"workers": ["stretch32", "wheezy32"]
}


In diesem Fall wird die Buildbot-Konfigurationsdatei komplizierter, da die Assemblys in verschiedenen Umgebungen miteinander verbunden werden sollen (wenn mindestens eine Umgebung ausfällt, wurde das gesamte Commit als nicht funktionsfähig angesehen). Zwei Ebenen helfen uns, das Problem zu lösen. Wir werden einen lokalen Mitarbeiter haben, der die .buildbot- Datei analysiert und die Builds für die gewünschten Mitarbeiter ausführt . Zunächst schreiben wir wie im vorherigen Beispiel unseren Schritt zur Analyse der .buildbot- Datei. Um die Assembly für einen bestimmten Worker zu starten, werden ein Bundle aus dem Trigger- Schritt und eine spezielle Art von TriggerableScheduler- Schedulern verwendet . Unser Schritt ist etwas komplizierter, aber durchaus verständlich geworden:

Klasse AnalyseSchritt
 class AnalyseStep(ShellMixin, BuildStep): def __init__(self, workdir, **kwargs): kwargs = self.setupShellMixin(kwargs, prohibitArgs = ['command', 'workdir', 'want_stdout']) BuildStep.__init__(self, **kwargs) self.workdir = workdir @defer.inlineCallbacks def _getWorkerList(self): cmd = RemoteShellCommand( command = ['cat', '.buildbot'], workdir = self.workdir, want_stdout = True, want_stderr = True, collectStdout = True ) cmd.useLog(self.stdio_log) yield self.runCommand(cmd) if cmd.didFail(): defer.returnValue([]) # parse JSON try: payload = json.loads(cmd.stdout) workers = payload.get('workers', []) except json.decoder.JSONDecodeError as e: raise ValueError('Error loading JSON from .buildbot file: {}' .format(str(e))) defer.returnValue(workers) @defer.inlineCallbacks def run(self): self.stdio_log = yield self.addLog('stdio') try: workers = yield self._getWorkerList() except ValueError as e: yield self.stdio_log.addStdout(str(e)) defer.returnValue(util.FAILURE) results = [] for worker in workers: results.append(steps.Trigger( name = 'check on worker "{}"'.format(worker), schedulerNames = ['Pet Project ({}) Scheduler'.format(worker)], waitForFinish = True, haltOnFailure = True, warnOnWarnings = True, updateSourceStamp = False, alwaysUseLatest = False ) ) self.build.addStepsAfterCurrentStep(results) defer.returnValue(util.SUCCESS) 


Wir werden diesen Schritt auf den lokalen Arbeiter anwenden. Bitte beachten Sie, dass wir das Tag auf unseren Sammler "Pet Project Builder" gesetzt haben. Damit können wir MailNotifier filtern und festlegen , dass Briefe nur an bestimmte Sammler gesendet werden sollen. Wenn diese Filterung nicht durchgeführt wird, erhalten wir beim Erstellen des Commits in zwei Umgebungen drei Buchstaben.

Allgemeiner Sammler
 factory = util.BuildFactory() factory.addStep(steps.Git( repourl = util.Property('repository'), workdir = 'sources', haltOnFailure = True, submodules = True, progress = True, mode = 'incremental') ) factory.addStep(AnalyseStep( name = 'Analyse .buildbot file', workdir = 'sources', haltOnFailure = True, hideStepIf = lambda results, s: results == util.SUCCESS) ) c['builders'] = [util.BuilderConfig( name = 'Pet Project Builder', tags = ['generic_builder'], workernames = ['local'], factory = factory )] 


Wir müssen nur die Sammler und die gleichen auslösbaren Scheduler für alle unsere echten Arbeiter hinzufügen:

Sammler in der richtigen Umgebung
 for worker in allWorkers: c['schedulers'].append(schedulers.Triggerable( name = 'Pet Project ({}) Scheduler'.format(worker), builderNames = ['Pet Project ({}) Builder'.format(worker)]) ) c['builders'].append(util.BuilderConfig( name = 'Pet Project ({}) Builder'.format(worker), workernames = [worker], factory = specific_factory) ) 



(Seite unseres Projekts in zwei Umgebungen erstellen)

Beispiel 4: Ein Buchstabe pro mehrere Commits


Wenn Sie eines der obigen Beispiele verwenden, können Sie eine unangenehme Funktion feststellen. Da für jedes Commit ein Buchstabe erstellt wird, erhalten wir 20 Buchstaben, wenn wir den Zweig mit 20 neuen Commits verschieben. Wenn Sie dies vermeiden, wie im vorherigen Beispiel, helfen wir auf zwei Ebenen. Wir müssen auch die Klasse ändern, um die Änderungen zu erhalten. Anstatt viele Änderungsobjekte zu erstellen, erstellen wir nur ein solches Objekt, in dessen Eigenschaften eine Liste aller Commits übertragen wird. In Eile kann dies folgendermaßen geschehen:

Klasse MultiGitHubHandler
 class MultiGitHubHandler(GitHubHandler): def getChanges(self, request): new_changes = GitHubHandler.getChanges(self, request) if not new_changes: return ([], 'git') change = new_changes[-1] change['revision'] = '{}..{}'.format( new_changes[0]['revision'], new_changes[-1]['revision']) commits = [c['revision'] for c in new_changes] change['properties']['commits'] = commits return ([change], 'git') c['www']['change_hook_dialects'] = { 'base': { 'custom_class': MultiGitHubHandler } } 


Um mit solch einem ungewöhnlichen Änderungsobjekt arbeiten zu können, benötigen wir unseren eigenen speziellen Schritt, der dynamisch Schritte erstellt, die ein bestimmtes Commit erfassen:

Klasse GenerateCommitSteps
 class GenerateCommitSteps(BuildStep): def run(self): commits = self.getProperty('commits') results = [] for commit in commits: results.append(steps.Trigger( name = 'Checking commit {}'.format(commit), schedulerNames = ['Pet Project Commits Scheduler'], waitForFinish = True, haltOnFailure = True, warnOnWarnings = True, sourceStamp = { 'branch': util.Property('branch'), 'revision': commit, 'codebase': util.Property('codebase'), 'repository': util.Property('repository'), 'project': util.Property('project') } ) ) self.build.addStepsAfterCurrentStep(results) return util.SUCCESS 


Fügen Sie unseren gemeinsamen Sammler hinzu, der nur für die Ausführung von Assemblys einzelner Commits zuständig ist. Es sollte markiert werden, um dann das Senden von Briefen nach diesem Tag selbst zu filtern.

General Mail Fetcher
 c['schedulers'] = [schedulers.AnyBranchScheduler( name = 'Pet Project Branches Scheduler', treeStableTimer = None, change_filter = util.ChangeFilter(project = 'Pet Project'), builderNames = ['Pet Project Branches Builder'] )] branches_factory = util.BuildFactory() branches_factory.addStep(GenerateCommitSteps( name = 'Generate commit steps', haltOnFailure = True, hideStepIf = lambda results, s: results == util.SUCCESS) ) c['builders'] = [util.BuilderConfig( name = 'Pet Project Branches Builder', tags = ['branch_builder'], workernames = ['local'], factory = branches_factory )] 


Es bleibt nur der Kollektor für einzelne Commits hinzuzufügen. Wir kennzeichnen diesen Sammler nur nicht mit einem Tag, daher werden keine Buchstaben dafür erstellt.

General Mail Fetcher
 c['schedulers'].append(schedulers.Triggerable( name = 'Pet Project Commits Scheduler', builderNames = ['Pet Project Commits Builder']) ) c['builders'].append(util.BuilderConfig( name = 'Pet Project Commits Builder', workernames = ['stretch32'], factory = specific_factory) ) 


Letzte Worte


Dieser Artikel ersetzt in keiner Weise das Lesen der offiziellen Dokumentation. Wenn Sie also an Buildbot interessiert sind, sollten Sie ihn als Nächstes lesen. Vollversionen der Konfigurationsdateien aller Beispiele sind auf dem Github verfügbar. Verwandte Links, von denen die meisten Materialien für den Artikel stammen:

  1. Offizielle Dokumentation
  2. Projektquellcode

Source: https://habr.com/ru/post/de439096/


All Articles