Git
Chapters ▾ 2nd Edition

6.5 GitHub - Skripte mit GitHub

Skripte mit GitHub

Jetzt haben wir alle wichtigen Funktionen und Workflows von GitHub kennengelernt, aber jede große Gruppe oder jedes Projekt wird Anpassungen haben, die sie vornehmen möchte, oder externe Dienste, die sie integrieren möchte.

Glücklicherweise ist GitHub, in vielerlei Hinsicht, ziemlich leicht anzupassen. In diesem Abschnitt erfährst du, wie du das GitHub-Hook-System und seine API verwendest, damit GitHub so funktioniert, wie wir es uns wünschen.

Dienste und Hooks

Der Bereich Hooks und Services der GitHub-Repository-Administration ist der einfachste Weg, um GitHub mit externen Systemen interagieren zu lassen.

Dienste

Schauen wir uns zuerst die Services (Dienste) an. Sowohl die Hooks- als auch die Dienste-Integration findest du im Abschnitt Einstellungen deines Repositorys. Dort haben wir uns zuvor mit dem Hinzufügen von Mitwirkenden und dem Ändern des Standard-Branch deines Projekts beschäftigt. Unter der Registerkarte „Webhooks und Dienste“ siehst du so etwas wie Konfiguration von Diensten und Hooks.

Dienste und Hooks
Abbildung 129. Konfiguration von Diensten und Hooks

Es gibt Dutzende von Diensten, aus denen du wählen kannst. Die meisten davon sind Integrationen in andere kommerzielle und Open-Source-Systeme. Die meisten von ihnen betreffen kontinuierliche Integrationsdienste (engl. Continuous-Integration-Services), Bug- und Issue-Tracker, Chatroom-Systeme und Dokumentationssysteme. Wir werden uns mit der Konfiguration eines sehr einfachen Systems befassen, dem E-Mail-Hook. Wenn du „E-Mail“ aus der Auswahlliste „Add Service“ wählst, erhältst du einen Konfigurationsbildschirm wie unter E-Mail-Service-Konfiguration.

E-Mail-Service
Abbildung 130. E-Mail-Service-Konfiguration

Wenn wir in diesem Fall auf die Schaltfläche „Dienst hinzufügen“ klicken, erhält die von uns angegebene Mail-Adresse jedes Mal eine E-Mail, wenn jemand in das Repository pusht. Dienste können auf viele verschiedene Arten von Ereignissen lauschen, aber die meisten sind ausschließlich auf Push-Events spezialisiert und bearbeiten diese Daten dann.

Wenn es ein System gibt, das du verwendest und das du mit GitHub integrieren möchtest, solltest du hier überprüfen, ob es eine bestehende Service-Integration gibt. Angenommen, du verwendest Jenkins, um auf deiner Code-Basis Tests durchzuführen. Du könntest die eingebaute Service-Integration von Jenkins aktivieren, um jedes Mal einen Testlauf zu starten, wenn jemand in dein Repository pusht.

Hooks

Wenn du eine speziellere Lösung benötigst oder mit einem Dienst oder einer Website interagieren möchtest, der nicht in dieser Liste enthalten ist, kannst du stattdessen das generischere Hooks-System verwenden. GitHub Repository-Hooks sind denkbar einfach. Gib eine URL an und GitHub wird bei jedem gewünschten Event über HTTP Nutz-Daten an diese URL senden.

Im Regelfall kannst du einen kleinen Webservice einrichten, um nach einem GitHub-Hook-Inhalt zu suchen und dann die empfangenen Daten weiter zu verarbeiten.

Um einen Hook zu aktivieren, klicke in Konfiguration von Diensten und Hooks auf die Schaltfläche „Webhook hinzufügen“. Das führt dich zu einer Seite, die wie Web-Hook Konfiguration aussieht.

Web-Hook
Abbildung 131. Web-Hook Konfiguration

Die Konfiguration für einen Web-Hook ist relativ einfach. In den meisten Fällen gibst du einfach eine URL und einen geheimen Schlüssel ein und klicken auf „Webhook hinzufügen“. Es gibt ein paar Optionen, bei denen GitHub veranlasst wird dir eine Payload zu senden – die Vorgabe ist, eine Payload nur für das push Ereignis senden, wenn jemand neuen Code in einen beliebigen Branch deines Repositorys schiebt.

Schauen wir uns ein kleines Beispiel für einen Webservice an, den du für die Verwaltung eines Web-Hooks einrichten kannst. Wir verwenden das Ruby Web-Framework Sinatra, da es relativ übersichtlich ist und du leicht sehen kannst, was wir tun.

Nehmen wir an, wir wollen eine E-Mail erhalten, wenn eine bestimmte Person zu einem bestimmten Branch unseres Projekts pusht und eine bestimmte Datei ändert. Mit einem solchen Code könnten wir das ziemlich einfach machen:

require 'sinatra'
require 'json'
require 'mail'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON

  # gather the data we're looking for
  pusher = push["pusher"]["name"]
  branch = push["ref"]

  # get a list of all the files touched
  files = push["commits"].map do |commit|
    commit['added'] + commit['modified'] + commit['removed']
  end
  files = files.flatten.uniq

  # check for our criteria
  if pusher == 'schacon' &&
     branch == 'ref/heads/special-branch' &&
     files.include?('special-file.txt')

    Mail.deliver do
      from     'tchacon@example.com'
      to       'tchacon@example.com'
      subject  'Scott Changed the File'
      body     "ALARM"
    end
  end
end

Hier nehmen wir den JSON-Inhalt, den GitHub uns liefert, und schauen nach, wer sie zu welchem Branch gepusht hat und welche Dateien bei allen Commits, die gepusht wurden, angefasst wurden. Dann überprüfen wir das anhand unserer Kriterien und senden eine E-Mail, wenn sie den Anforderungen entspricht.

Um so etwas zu entwickeln und zu testen, hast du eine ansprechende Entwicklerkonsole auf dem gleichen Bildschirm, auf dem du den Hook eingerichtet hast. Du kannst die jüngsten Aktualisierungen sehen, die GitHub für diesen Webhook vorgenommen hat. Für jeden Hook kannst du nachvollziehen, wann er zugestellt wurde, ob er erfolgreich war und Body und Header für Anfrage (engl. request) und Antwort (engl. response) prüfen. Das ermöglicht ein unglaublich einfaches Testen und Debuggen deines Hooks.

Web-Hook Debugging
Abbildung 132. Web-Hook Debug Information

Das andere großartige Feature ist, dass du jede der Payloads neu ausliefern kannst, um deinen Service einfach zu testen.

Weitere Informationen wie man Webhook schreiben kann und welche Event-Typen man überwachen kann, findest du in der GitHub-Developer-Dokumentation unter https://developer.github.com/webhooks/.

Die GitHub API

Dienste und Hooks bieten die die Möglichkeit, Push-Benachrichtigungen über Ereignisse zu erhalten, die in deinem Repositorys stattfinden. Aber was ist, wenn du weitere Informationen über diese Ereignisse benötigst? Was ist, wenn du eine Automatisierung benötigst, wie z.B. das Hinzufügen von Mitwirkenden oder das Labeln von Issues?

Hier kommt die GitHub API zum Zug. GitHub verfügt über eine Vielzahl von API-Endpunkten, um fast alles zu automatisieren, was du manuell auf der Website tun kannst. In diesem Abschnitt erfahren wir, wie man sich authentifiziert und mit der API verbindet, wie man ein Issue kommentiert und wie man den Status eines Pull-Requests über die API ändert.

Grundlegende Anwendung

Die elementarste Aufgabe, die du lösen kannst, ist eine einfache GET-Anfrage an einen Endpunkt, der keine Authentifizierung erfordert. Das kann ein Benutzer- oder Lese-Informationen in einem Open-Source-Projekt sein. Wenn wir beispielsweise mehr über einen Benutzer mit Namen „schacon“ erfahren möchten, können wir so etwas verwenden:

$ curl https://api.github.com/users/schacon
{
  "login": "schacon",
  "id": 70,
  "avatar_url": "https://avatars.githubusercontent.com/u/70",
# …
  "name": "Scott Chacon",
  "company": "GitHub",
  "following": 19,
  "created_at": "2008-01-27T17:19:28Z",
  "updated_at": "2014-06-10T02:37:23Z"
}

Es gibt unzählige Endpunkte wie diesen, um Informationen über Organisationen, Projekte, Issues, Commits zu erhalten – so ziemlich alles, was du öffentlich auf GitHub sehen kannst. Du kannst die API sogar verwenden, um beliebige Markdown-Funktionen zu rendern oder eine .gitignore Vorlage zu finden.

$ curl https://api.github.com/gitignore/templates/Java
{
  "name": "Java",
  "source": "*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
"
}

Ein Issue kommentieren

Wenn du jedoch eine Aktivität auf der Website durchführen möchtest, wie z.B. einen Kommentar zu einem Issue oder Pull Request oder wenn du private Inhalte einsehen oder mit diesen interagieren möchtest, musst du sich authentifizieren.

Es gibt mehrere Möglichkeiten, sich zu authentifizieren. Du kannst die Basisauthentifizierung nur mit deinem Benutzernamen und Passwort verwenden, aber im Allgemeinen ist es eine bessere Idee, einen persönlichen Zugriffstoken zu verwenden. Du kannst den über die Registerkarte „Anwendungen“ auf deiner Einstellungsseite generieren.

Access Token
Abbildung 133. Generieren eines Zugriffstokens auf der Registerkarte „Anwendungen“ der Settings-Seite

Du wirst gefragt, welchen ​​Geltungsbereich du für dieses Token möchtest und es wird eine Beschreibung angezeigt. Achte darauf, eine gute Beschreibung zu verwenden, damit du dir sicher bist, das Token entfernen zu können, wenn dein Skript oder deine Anwendung nicht mehr verwendet wird.

GitHub zeigt dir den Token nur ein einziges Mal an, also kopiere und speicher ihn sorgfältig ab. Du kannst diese Funktion nun verwenden, um dich in deinem Skript zu authentifizieren, anstatt einen Benutzernamen und ein Passwort zu verwenden. Das ist angenehm, weil du den Umfang dessen, was du tun möchtest, einschränken kannst und das Token widerruflich ist.

Das hat auch den Vorteil, dass die Abfrage-Rate erhöht wird. Ohne Authentifizierung bist du auf 60 Anfragen pro Stunde beschränkt. Wenn du dich authentifizierst, kannst du bis zu 5.000 Anfragen pro Stunde stellen.

Also nutzen wir es, um einen Kommentar zu einem unserer Issues abzugeben. Nehmen wir an, wir wollen einen Kommentar zu einem bestimmten Problem, Issue #6, abgeben. Dazu müssen wir einen HTTP POST Request an repos/<user>/<repo>/issues/<num>/comments mit dem Token stellen, den wir gerade als Autorisierungs-Header generiert haben.

$ curl -H "Content-Type: application/json" \
       -H "Authorization: token TOKEN" \
       --data '{"body":"A new comment, :+1:"}' \
       https://api.github.com/repos/schacon/blink/issues/6/comments
{
  "id": 58322100,
  "html_url": "https://github.com/schacon/blink/issues/6#issuecomment-58322100",
  ...
  "user": {
    "login": "tonychacon",
    "id": 7874698,
    "avatar_url": "https://avatars.githubusercontent.com/u/7874698?v=2",
    "type": "User",
  },
  "created_at": "2014-10-08T07:48:19Z",
  "updated_at": "2014-10-08T07:48:19Z",
  "body": "A new comment, :+1:"
}

Wenn du jetzt zu diesem Issue gehst, kannst du den Kommentar sehen, den wir gerade erfolgreich gepostet haben, wie in Kommentar, veröffentlicht von der GitHub API zu sehen ist.

API Kommentar
Abbildung 134. Kommentar, veröffentlicht von der GitHub API

Du kannst die API verwenden, um so ziemlich alles zu tun, was du auf der Website tun kannst – das Erstellen und Setzen von Meilensteinen, das Zuweisen von Personen zu Issues und Pull-Requests, das Erstellen und Ändern von Labels, auf Commit-Daten zugreifen, das Erstellen neuer Commits und Branches, das Öffnen, Schließen oder Mergen von Pull-Requests, das Erstellen und Bearbeiten von Teams, das Kommentieren von Code-Zeilen in einem Pull-Request, das Durchsuchen der Website und so weiter und so fort.

Den Status eines Pull-Requests ändern

Ein abschließendes Beispiel werden wir uns ansehen, da es wirklich praktisch ist, wenn du mit Pull-Requests arbeitest. Jeder Übertragung können ein oder mehrere Zustände zugeordnet sein. Es gibt eine API für das Hinzufügen und Abfragen dieser Stati.

Die meisten der Dienste für kontinuierliche Integration und Tests nutzen diese API, um auf Pushes zu reagieren, indem sie den Code testen, der gepusht wurde, und dann Bericht erstatten, wenn dieser Commit alle Tests bestanden hat. Du kannst damit auch überprüfen, ob die Commit-Nachricht korrekt formatiert ist, ob der Einreicher alle deine Contributions-Richtlinien befolgt hat, ob die Übertragung gültig signiert wurde – und vieles mehr.

Angenommen, du richtest einen Webhook in deinem Repository ein, der einen kleinen Webdienst aufruft, der in der Commit-Nachricht nach einer Zeichenkette Signed-off-by sucht.

require 'httparty'
require 'sinatra'
require 'json'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON
  repo_name = push['repository']['full_name']

  # look through each commit message
  push["commits"].each do |commit|

    # look for a Signed-off-by string
    if /Signed-off-by/.match commit['message']
      state = 'success'
      description = 'Successfully signed off!'
    else
      state = 'failure'
      description = 'No signoff found.'
    end

    # post status to GitHub
    sha = commit["id"]
    status_url = "https://api.github.com/repos/#{repo_name}/statuses/#{sha}"

    status = {
      "state"       => state,
      "description" => description,
      "target_url"  => "http://example.com/how-to-signoff",
      "context"     => "validate/signoff"
    }
    HTTParty.post(status_url,
      :body => status.to_json,
      :headers => {
        'Content-Type'  => 'application/json',
        'User-Agent'    => 'tonychacon/signoff',
        'Authorization' => "token #{ENV['TOKEN']}" }
    )
  end
end

Das ist hoffentlich relativ einfach zu verstehen. In diesem Web-Hook-Handler schauen wir uns jeden Commit an, der gerade gepusht wurde, wir suchen nach der Zeichenkette 'Signed-off-by' in der Commit-Nachricht und POST(en) via HTTP den Status an den API-Endpunkt /repos/<user>/<repo>/statuses/<commit_sha>.

In diesem Fall kannst du einen Zustand ('success', 'failure', 'error'), eine Beschreibung des Geschehens, eine Ziel-URL, auf die der Benutzer für weitere Informationen zugreifen kann, und einen „Kontext“ senden, falls es mehrere Zustände für einen einzelnen Commit gibt. So kann beispielsweise ein Testdienst einen Status liefern und ein Validierungsdienst wie dieser ebenfalls einen Status – das Feld „Kontext“ zeigt, wie sie sich voneinander unterscheiden.

Wenn jemand einen neuen Pull-Request auf GitHub öffnet und dieser Hook eingerichtet ist, siehst du vielleicht etwas wie Commit-Status via API.

Commit-Status
Abbildung 135. Commit-Status via API

Du siehst nun ein kleines grünes Häkchen neben dem Commit, der in der Nachricht die Zeichenkette „Signed-off-by“ enthält und ein rotes Kreuz, wenn der Autor vergessen hat, den Commit zu signieren. Du kannst auch sehen, dass der Pull-Request den Status des letzten Commits auf dem Branch annimmt und dich warnt, falls es einen Fehler gibt. Das ist besonders nützlich, wenn du diese API für Prüfergebnisse verwendest, damit du nicht versehentlich etwas zusammenführst, bei dem der letzte Commit die Tests nicht besteht.

Octokit

Obwohl wir in diesen Beispielen fast alles durch curl und einfache HTTP-Requests gemacht haben, gibt es mehrere Open-Source-Bibliotheken, die diese API auf eine eigenständigere Form verfügbar machen. Zum Zeitpunkt des Entstehen dieses Buchs umfassen die unterstützten Sprachen Go, Objective-C, Ruby und .NET. Besuche https://github.com/octokit für weitere Informationen zu diesen Themen, da sie einen großen Teil des HTTP-Codes für dich übernehmen.

Hoffentlich können dir diese Tools helfen, GitHub anzupassen und zu modifizieren, um sich so besser an deinem individuellen Workflows anzupassen. Eine vollständige Dokumentation der gesamten API sowie Anleitungen für häufige Aufgaben findest du unter https://developer.github.com.

scroll-to-top