Dieser Post ist Teil 3 meiner Serie „Source Management in SFDX“. In den ersten beiden Teilen habe ich die Anforderungen analysiert, ein Konzept vorgestellt und ein Template entwickelt. In diesem Teil zeige ich, wie ich mit SFDX eine Continuous Integration (CI) Pipeline mit CircleCI aufsetze und das Deployment mit 2nd Generation Packages bis Produktiv voll automatisiere.
- Teil 1: Anforderungen und Konzept
- Teil 2: Ordnerstruktur und Prozesse
- Teil 3: CI mit CircleCI (Dieser Post)
Die Pipeline in CircleCI besteht aus drei Jobs, die jeweils um zusätzliche Steps (z.B. Deployments von unpackaged Source, Jest Tests, Linting, Statische Code Analyse) erweitert werden können:
- Scratch Org erstellen, Abhängigkeiten des Projekts installieren, Source Pushen, Package Tests durchführen
- Beta Package Version erstellen, auf einer Staging Umgebung installieren und vollen Regressions Testlauf auf Staging durchführen (dieser Job wird sinnvoller Weise nur nach manuellem Approval durchgeführt)
- Production Package Version erstellen, Promoten, auf Staging & Production installieren und vollen Regressions Testlauf auf Production durchführen (dieser Job wird sinnvoller Weise nur nach manuellem Approval durchgeführt)
CircleCI für SFDX Projekte
Um eine vollwertige CI Pipeline für SFDX aufzubauen, muss man zuerst die elementaren Phasen des Entwicklungsprozesses einer 2nd Generation Package Version verstehen. Egal wie komplex die Anwendung ist, im Wesentlichen durchläuft jede Package Version die folgenden drei Phasen:
- Lokale Entwicklung mit fixen Abhängigkeiten auf einer Scratch Org.
- Integrations Test der Package Version mit den tatsächlichen Abhängigkeiten auf einer Integrations-Test Umgebung
- Finales Packaging und Release auf eine Produktiv Umgebung
Jede dieser Phasen stellt unterschiedliche Anforderungen an eine CI Pipeline und wird deshalb in einem eigenen Job implementiert. Alle Jobs teilen sich den selben Setup Code:
- Installation der CLI über den offiziellen Orb von CircleCI: circleci/[email protected].
- Autorisierung der CLI mit dem DevHub (Production) und allen Staging oder Integration Umgebungen (Sandboxes).
- Installation von NPM (falls es auf dem verwendeten Image nicht schon vorinstalliert ist) und installation der node.js Abhängigkeiten.
Jobs
Ein Job in CircleCI ist eine wiederverwendbare Serie von Einzelschritten (Steps). Sie werden immer in ihrem eigenen Container ausgeführt, das bedeutet, dass jeder Job die CLI einrichten und autorisieren muss. Ich implementiere für jede Phase einen eigenen Job und orchestriere diese über einen Workflow.
Eine komplette Implementierung, die mit wenigen Anpassungen lauffähig ist, habe ich in meinem Template Repository für den 1-1-1 Setup bereitgestellt: https://github.com/j-schreiber/1-1-1-setup-template
Scratch Org Test
Die erste und simpelste Phase dieser Pipeline. Dieser Job kann fast problemlos parallelisiert werden, da wir ausschließlich mit Scratch Orgs arbeiten (das einzige relevante Limit sind 40 gleichzeitig aktive Scratch Orgs, auf die die meisten DevHubs limitiert sind).
Run Org Setup
: Erstellt eine Scratch Org mit dem Setup Script dieses Repos. Warum? Damit gewährleiste ich, dass das SFDX Projekt als Ganzes immer funktioniert. Da der Setup in jedem Projekt individuell ist, können wir dasconfig.yml
ohne große Anpassungen für jedes Projekt wiederverwenden. Das Setup Script führt in der Regel folgende Schritte aus:- Erstellen der Scratch Org mit der
scratch-org-def.json
des Projekts. - Installation der Dependencies des Projekts – Die Installation Keys werden als Environment Variable (dazu später mehr) gespeichert, während die Subscriber Package Version Ids in der
sfdx-project.json
gepflegt werden. - Push des Sources dieses Packages auf die Scratch Org (
sfdx force:source:push
) - Zuweisen von Berechtigungssätzen an den Admin User, die für die Entwicklung benötigt werden
- Import der Testdaten über
sfdx force:data:tree:import
- Individuelle weitere Schritte, welche für die Entwicklung sinnvoll sind wie z.B. Apex-Skripte für erweiterten Test Daten Setup (Preisbucheinträge, Community User aktivieren, usw) oder Deployment von Unpackaged Source.
- Erstellen der Scratch Org mit der
Run Tests (Scratch Org)
: Führt einen Apex Test Run auf der Scratch Org durch. Dieser Run testet nur das Package mit seinen Abhängigkeiten. Das sind nicht zwangsläufig auch die tatsächlich installierten Abhängigkeiten auf Integration oder Production.- Optional weitere Tests wie Linting (z.B. mit dem sfdx-scanner plugin) oder Jest Tests durchführen, falls das Projekt LWC’s hat.
Clean Scratch Org
und undClean Duplicate Test Results
(ein Fehler insfdx force:apex:test:run
erzeugt immer zwei Dateien, was bei CircleCI dazu führt, dass alle Tests doppelt hochgeladen und gezählt werden)
Beispiel config.yml
für den scratch_org_test Job:
Und hier ein Beispiel dieser Config in einem produktiven Lauf:
Build And Install Staging (Integrations Test)
Die zweite Phase ist etwas komplizierter, da sich ab hier einzelne Entwickler unter Umständen in die Quere kommen könnten: Auf einer Sandbox kann immer nur ein Test-Lauf gleichzeitig durchgeführt werden. Läuft also gerade eine Pipeline, sind alle anderen Pipelines blockiert und scheitern. Deshalb sollte der zweite Job nicht automatisch durchgeführt werden, sondern nur nach manueller Freigabe von z.B. einem Release Manager bzw. in Absprache mit anderen Entwicklern.
Build Beta Package
: Erstellen einer Beta-Package Version ohne Validierung. Der Installation Key ist als Projekt Environment Variable in CircleCI gespeichert.Install Package (Staging)
: Installation des Packages auf einer Staging oder Integrations Umgebung.Deploy Source (Staging)
: Deploy von zusätzlichem Source der nicht Teil des Packages aber Teil des Projekts ist (z.B. Workflow Email Alerts mit organisationsweiten Adressen oder Übersetzungen)Run Tests (Staging)
: Testlauf mit den tatsächlich installierten Abhängigkeiten und weiterem Source (Unpackaged Metadata, weitere Packages, etc) auf der Staging Umgebung.Clean Duplicate Test Results
: Aufräumen der doppelt erzeugten Testergebnisse.
Beispiel config.yml
für den build_and_install_staging job:
Build And Install Production
Der Production Job ist definitiv der komplizierteste. Zusätzlich zu den Besonderheiten auf Staging ist der Job nicht mehr idempotent: Sobald eine Package Version einmal promoted wurde, kann kein weiterer Build dieser Package Version mehr promoted werden. Sie sollte deshalb ebenfalls nur nach manuellem Approval und Absprache aller Entwickler ausgeführt werden. Mein Workflow ist so konfiguriert, dass nur Pull Requests von einem version/
Branch den Production Job starten.
Build Production Package
: Erzeugen einer Package Version mit Validierung und Test Coverage. Dieser Schritt dauert je nach Größe des Packages 5 bis 15 Minuten. Die Wahrscheinlichkeit, dass der Package Build scheitert, ist vergleichsweise gering aufgrund der Validierungen in den Jobs davor.Install Package (Staging)
: Installation des (validierten) Builds auf Staging. Der Build sollte sich nicht vom Build aus dem Staging Job unterscheiden.Deploy Source (Staging)
: Deployment von unpackaged Source des Projekts auf Staging. Bei diesem Deployment aktiviere ich zusätzlich den Flag-l RunLocalTests
. Das simuliert ein Deployment auf Produktiv und stellt sicher, dass die Pipeline frühzeitig failed, falls es Probleme gibt.Promote Package
: Ab diesem Schritt gibt es kein Zurück mehr: Ist der Package Build bereit für Production, wird er promoted.Install Package (Production)
: Installation des (validierten und promoteten) Builds auf Production.Deploy Source (Production)
: Deployment von unpackaged Source des Projekts auf Production. Hier muss der Flag-l RunLocalTests
aktiviert sein.Run Tests (Production)
: Vollständiger Testlauf mit allen dort installierten Abhängigkeiten auf Produktiv.Clean Duplicate Test Results
: Wie immer müssen doppelt erstellte Test Results gelöscht werden.
Beispiel config.yml
für den build_and_install_production job
Beispiel-Screenshot eines vollständigen Laufs. Der Job deployed zusätzlich noch den Source einer Community und veröffentlicht alle Änderungen der Community. Im konkreten Beispiel ist ein Test fehlgeschlagen. Da der Step jedoch mit set +e
ausgeführt wurde, läuft die Pipeline weiter.
Putting it all together … der Workflow
Da nicht alle Jobs unter allen Bedingungen durchgeführt werden sollen, orchestriere ich sie mit dem Workflow. Hier haben wir die Möglichkeit, einzelne Schritte nur bei Bedarf auszuführen (falls man sich z.B. vorbehalten möchte, nur nach manueller Prüfung auf Produktiv zu deployen …).
Wir verwenden bei TMH ein feature/
und version/
Branching Model. Feature branches werden in Version Branches gemerged (mit Pull Requests) und Version Branches in Master.
- Feature Branches durchlaufen nur die Jobs scratch_org_test und build_and_install_staging aus.
- Version Branches durchlaufen zusätzlich noch den Job build_and_install_production.
CircleCI für SFDX konfigurieren
Damit CircleCI bei jedem Build die SFDX CLI sicher mit dem DevHub und allen Sandboxes autorisieren kann, sind einige spezifischen Konfigurationen notwendig.
Context Variables
In Context Variablen organisieren wir Variablen, die alle Projekte verwenden. Die Projekte müssen im Workflow mit context: - salesforce
den jeweiligen Context angeben, damit sie mit $VARIABLE_NAME
verfügbar sind:
- SFDX_CONSUMER_KEY: Der Consumer Key der Connected App für den DevHub
- SFDX_CONSUMER_KEY_STAGING: Der Consumer Key der Connected App für Staging Sandbox
- SFDX_JWT_KEY: Base64 encoded Zertifikat für die Connected App.
- USERNAME_PRODUCTION: Username für die Autorisierung auf Production. Der User muss pre-approved sein.
- USERNAME_STAGING: Username für die Autorisierung auf Staging. Der User muss pre-approved sein.
Project Environment Variables
In Project Environment Variables organisieren wir die lokalen Variablen für ein Projekt. Diese sind in jedem CircleCI Job mit $VARIABLE_NAME
immer verfügbar.
INSTALLATION_KEY
undPACKAGE_ID
(0Ho
) des Projekts (für Erstellung einer Package Version)- Installation Keys der Abhängigkeiten. Theoretisch kann man alle Installation Keys auch im Context speichern, muss dann aber für jedes Projekt die
config.yml
individualisieren.
Advanced Settings
Unter Project Settings > Advanced gibt es für SFDX zwei relevante Einstellungen:
- Only Build Pull Requests: Macht Sinn, da der komplexe Workflow in der Regel 20-30 Minuten läuft. Dieses Setting hilft vor allem, die parallel laufenden Pipelines einzuschränken.
- Auto-cancel Redundant Builds: Macht nur unter bestimmten Voraussetzungen Sinn, zum Beispiel wenn nur wenige Entwickler gleichzeitig auf einem Package arbeiten und nicht zu viele Pull-Requests für das selbe Projekt gleichzeitig offen sind.
Zusammenfassung
Wir verwenden bei TMH für jedes SFDX Projekt das 1-1-1 Template und haben den kompletten Build mit CircleCI automatisiert. Durch diesen Setup ist gewährleistet, dass wir die Salesforce Entwicklung in alle Richtungen skalieren können: Externe Entwickler brauchen nur Zugriff auf die Github Repositories mit denen sie arbeiten, aber keine Zugänge für Produktiv oder Staging. Onboarding neuer Entwickler und Einrichtung der Arbeitsumgebung dauert bei erfahrenen Entwicklern weniger als eine Stunde. Ein Entwickler arbeitet lokal auf seinem Feature Branch und kann mit einem Pull Request praktisch vollautomatisch eine Package Version für den UAT erzeugen und auf Staging deployen. Kein Entwickler kann mehr „vergessen“, einen vollständigen Regressionstest auszuführen.
Ich bin bei TMH der einzige Technical Architect und da her natürlicherweise der Flaschenhals. Um so besser, dass ich meine Zeit nicht mit dem Lösen von Merge Konflikten und repetitive Deployment Prozessen vergeuden muss, sondern mich inhaltlich auf den Code Review konzentrieren kann.