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 zu manipulieren. In diesem Abschnitt erfahren Sie, wie Sie das GitHub-Hook-System und seine API verwenden, 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 finden Sie im Abschnitt Einstellungen Ihres Repositorys, wo wir uns zuvor mit dem Hinzufügen von Mitwirkenden und dem Ändern der Standard-Branch Ihres Projekts beschäftigt haben. Unter der Registerkarte „Webhooks und Dienste“ sehen Sie so etwas wie Konfiguration von Diensten und Hooks.

Dienste und Hooks
Figure 129. Konfiguration von Diensten und Hooks

Es gibt Dutzende von Diensten, aus denen Sie wählen können, 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 Sie „E-Mail“ aus der Auswahlliste „Add Service“ wählen, erhalten Sie einen Konfigurationsbildschirm wie E-Mail-Service-Konfiguration.

E-Mail-Service
Figure 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 Sie verwenden und das Sie mit GitHub integrieren möchten, sollten Sie hier überprüfen, ob es eine bestehende Service-Integration gibt. Angenommen, Sie verwenden Jenkins, um auf Ihrer Code-Basis Tests durchzuführen, können Sie die eingebaute Service-Integration von Jenkins aktivieren, um jedes Mal einen Testlauf zu starten, wenn jemand in Ihr Repository pusht.

Hooks

Wenn Sie eine speziellere Lösung benötigen oder mit einem Dienst oder einer Website integrieren möchten, der nicht in dieser Liste enthalten ist, können Sie stattdessen das generischere Hooks-System verwenden. GitHub Repository-Hooks sind denkbar einfach. Geben Sie eine URL an und GitHub wird bei jedem gewünschten Event über HTTP Nutz-Daten an diese URL senden.

Im Regelfall können Sie einen kleinen Webservice einrichten, um nach einer GitHub-Hook-Nutzlast (engl. payload) zu suchen und dann die empfangenen Daten weiter zu verarbeiten.

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

Web-Hook
Figure 131. Web-Hook Konfiguration

Die Konfiguration für einen Web-Hook ist relativ einfach. In den meisten Fällen geben Sie 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 Ihnen eine Payload zu senden – die Vorgabe ist, eine Payload nur für das push Ereignis senden, wenn jemand neuen Code in einen beliebigen Branch Ihres Repositorys schiebt.

Schauen wir uns ein kleines Beispiel für einen Webservice an, den Sie für die Verwaltung eines Web-Hooks einrichten können. Wir verwenden das Ruby Web-Framework Sinatra, da es relativ übersichtlich ist und Sie leicht sehen können sollten, 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 die JSON-„Nutzlast“, die 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, haben Sie eine ansprechende Entwicklerkonsole auf dem gleichen Bildschirm, auf dem Sie den Hook eingerichtet haben. Sie können die jüngsten Aktualisierungen sehen, die GitHub für diesen Webhook vorgenommen hat. Für jeden Hook können Sie 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 Ihrer Hooks.

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

Das andere großartige Feature ist, dass Sie jede der Payloads neu ausliefern können, um Ihren Service einfach zu testen.

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

Die GitHub API

Dienste und Hooks bieten Ihnen die Möglichkeit, Push-Benachrichtigungen über Ereignisse zu erhalten, die in Ihren Repositories stattfinden, aber was ist, wenn Sie weitere Informationen über diese Ereignisse benötigen? Was ist, wenn Sie eine Automatisierung benötigen, wie z.B. das Hinzufügen von Mitwirkenden oder das Markieren von Problemen?

Hier kommt die GitHub API zum Zug. GitHub verfügt über eine Vielzahl von API-Endpunkten, um fast alles zu tun, was Sie auf der Website automatisiert tun können. 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 Sie lösen können, ist eine einfache GET-Anfrage an einen Endpunkt, der keine Authentifizierung erfordert. Das kann ein Benutzer oder schreibgeschützte Informationen zu 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 Sie öffentlich auf GitHub sehen können. Sie können 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 Sie jedoch eine Aktivität auf der Website durchführen möchten, wie z.B. einen Kommentar zu einem Issue oder Pull Request oder wenn Sie private Inhalte einsehen oder mit diesen interagieren möchten, müssen Sie sich authentifizieren.

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

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

Sie werden gefragt, welchen ​​Geltungsbereich Sie für dieses Token möchten und es wird eine Beschreibung angezeigt. Achten Sie darauf, eine gute Beschreibung zu verwenden, damit Sie sich sicher sind, das Token entfernen zu können, wenn Ihr Skript oder Ihre Anwendung nicht mehr verwendet wird.

GitHub zeigt Ihnen den Token nur ein einziges Mal an, also kopieren Sie ihn unbedingt. Sie können diese Funktion nun verwenden, um sich in Ihrem Skript zu authentifizieren, anstatt einen Benutzernamen und ein Passwort zu verwenden. Das ist angenehm, weil Sie den Umfang dessen, was Sie tun möchten, einschränken können und das Token widerruflich ist.

Das hat auch den Vorteil, dass die Rate erhöht wird. Ohne Authentifizierung sind Sie auf 60 Anfragen pro Stunde beschränkt. Wenn Sie sich authentifizieren, können Sie 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 Sie jetzt zu diesem Issue gehen, können Sie den Kommentar sehen, den wir gerade erfolgreich gepostet haben, wie in Kommentar, veröffentlicht von der GitHub API zu sehen ist.

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

Sie können die API verwenden, um so ziemlich alles zu tun, was Sie auf der Website tun können – das Erstellen und Setzen von Meilensteinen, das Zuweisen von Personen zu Issues und Pull-Requests, das Erstellen und Ändern von Labels, den Zugriff auf Commit-Daten, 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 Sie mit Pull-Requests arbeiten. 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 verschoben wurde, und dann Bericht erstatten, wenn dieser Commit alle Tests bestanden hat. Sie können damit auch überprüfen, ob die Commit-Nachricht korrekt formatiert ist, ob der Einreicher alle Ihre Contributions-Richtlinien befolgt hat, ob die Übertragung gültig signiert wurde – und vieles mehr.

Angenommen, Sie richten einen Webhook in Ihrem 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 an den /repos/<user>/<repo>/statuses/<commit_sha> API-Endpunkt mit dem Status.

In diesem Fall können Sie 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, sehen Sie vielleicht etwas wie Commit-Status via API.

Commit-Status
Figure 135. Commit-Status via API

Sie sehen nun ein kleines grünes Häkchen neben dem Commit, das in der Nachricht eine Zeichenkette „Signed-off-by“ und ein rotes Kreuz dasjenige enthält, bei dem der Autor vergessen hat, sich abzumelden. Sie können auch sehen, dass der Pull-Request den Status des letzten Commits auf dem Branch annimmt und Sie warnt, falls es ein Fehler ist. Das ist besonders nützlich, wenn Sie diese API für Prüfergebnisse verwenden, damit Sie nicht versehentlich etwas zusammenführen, 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. Besuchen Sie Octokit für weitere Informationen zu diesen Themen, da sie einen großen Teil des HTTP-Protokolls für Sie verarbeiten.

Hoffentlich können diese Tools Ihnen helfen, GitHub anzupassen und zu modifizieren, um so besser zu Ihren individuellen Workflows zu passen. Eine vollständige Dokumentation der gesamten API sowie Anleitungen für häufige Aufgaben finden Sie unter https://developer.github.com.