Docker Image für den Apple (Finanz) Reporter

Um Finanzberichte des Apple AppStores herunterzuladen, habe ich mir Docker zu eigen gemacht. Folgende Anleitung hilft bei den ersten Schritten.

Hintergrund

Mein erstes Point-And-Click Adventure Argh! Earthlings! ist ja bekanntlich im App Store zu haben. Viele Spieler schätzen die Arbeit daran und gönnen sich das Spiel, indem sie einen verhältnismäßig kleinen Preis von ein paar Euro1 zahlen. Damit kann ich dann Kosten für Lizenzen, Webhosting, Arbeit der Übersetzer, Hardware und Steuern bezahlen. Die Einnahmen sammelt Apple für mich in Finanzberichten. Diese kann ich per iTunes-Connect herunterladen. Das kann ich mühsam per Hand machen, oder mir mit dem Reporter Tool helfen. Der Download beinhaltet ein Java-Archiv (jar), das eine Schnittstelle zu Apple's Finanzberichten bereitstellt, und einer Konfigurationsdatei. Der Vorteil ist, dass ich dieses Tool auf dem Terminal nutzen kann.

Nun musste ich bisher immer zuerst Java auf meinem Mac installieren, damit ich den Reporter nutzen konnte. Nach der jüngsten Aktivitäten um das JDK habe ich meinen Mac von Java befreien wollen und plane in Zukunft auf Java zu verzichten. Doch wie kann ich den Reporter nutzen ohne das JDK zu installieren? Hier halte ich Docker für sehr praktisch, denn inzwischen laufen Jekyll-Container in Docker auf meinem Mac und bauen sehr zuverlässig meine statische Homepage. Daher habe ich mich entschieden auch den Reporter mitsamt eines aktuellen JDK in einem Docker Container zu betreiben. Hier ist mein Weg:

Konzept

Im Grunde ist die Idee wie folgt: Reporter.jar und ein Download-Script werden in den Docker Image geladen. Wenn der Container startet soll das Download-Script die Finanzberichte herunterladen und für weitere Verarbeitung umformatieren.

Vorbereitung

Nach dem Download und dem Entpacken des Reporter-Archivs erscheinen die beiden Dateien Reporter.jar und Reporter.properties. Beide Dateien werden in den Ordner data abgelegt. Darin liegt auch ein run.sh Script, welches den Download der Reports und deren Konvertierung durchführt. Der gesamte Inhalt des data Ordners wird später in das Docker Image importiert. Zuvor muss aber die Datei Reporter.properties um die letzte Zeile, den AccessToken, vervollständigt werden. Den AccessToken muss man im iTunes-Connect generieren, siehe auch FAQ unten.

# Reporter.properties
Mode=Normal
SalesUrl=https://reportingitc-reporter.apple.com/reportservice/sales/v1
FinanceUrl=https://reportingitc-reporter.apple.com/reportservice/finance/v1
AccessToken=123456789-1234-1234-1234-1234567890123

Der Projektordner hat nun folgende Struktur.

data/Reporter.jar
data/Reporter.properties
data/run.sh
Dockerfile

Das Dockerfile sorgt für die korrekte Zusammenstellung des Image.

# Dockerfile
FROM openjdk:8-jre-alpine
RUN mkdir -p /opt/runner /data/output
WORKDIR /opt/runner
ENTRYPOINT [ "./run.sh" ]
COPY data/* .
RUN chmod 755 ./run.sh

Ich habe mich hier für ein OpenJDK 8 mit Alpine Linux Unterbau entschieden. Damit bleibt das ganze Paket mit ca 85 MB recht klein. Es werden zwei Ordner /opt/runner und /data/output angelegt. Der erste beinhaltet den Reporter und der zweite die späteren Berichte und die Zusammenfassung. Als Arbeitsverzeichnis gilt /opt/runner. Als Docker-Einstiegspunkt wird das Script ./run.sh festgelegt. Erst zum Schluß, werden alle nötigen Dateien aus dem data Ordner importiert. Dem Script wird die Ausführung per chmod gewährt. Das hat den Zweck, dass die Layer, die Docker anlegt erst so spät wie möglich variieren, und der Build Prozess schneller abläuft.

Last but not least muss nur noch das run.sh Script vorbereitet werden.

Augen auf! Alpine Linux stellt nur die Shell (sh) bereit, und NICHT die bash!

# !/bin/sh

# Hier muss die eigene VENDOR ID eingetragen werden, die von Apple je Account
# vergeben wird.

vendorId=12345678

# Einige Konstanten, die die Rechnerei mit dem Datum erleichtern.

currentday=$(date '+%d')
currentmonth=$(date '+%m')
fiscalyear=$(date '+%Y')

# Zuerst werden der Reihe nach alle vorhandenen Reports des laufenden Jahres geladen.
# Der letzte Report ist nie vorhanden. Hier wäre Verbesserungspotenzial.

for month in $(seq -w 01 $currentmonth); do
	fiscalmonth="${month}"
	fiscaldate="$fiscalyear$fiscalmonth"
	echo "Fetching fiscal date $fiscalyear-$fiscalmonth"
	java -jar Reporter.jar p=Reporter.properties m=Normal Sales.getReport \
		$vendorId, Sales, Summary, Monthly, $fiscaldate
done

# Dann werden der Reihe nach alle vorhandenen Reports des laufenden Monats geladen.
# Der letzte Report ist nie vorhanden. Hier wäre Verbesserungspotenzial.

for day in $(seq -w 01 $currentday); do
	fiscalmonth=${currentmonth}
	fiscaldate="$fiscalyear$fiscalmonth$day"
	echo "Fetching fiscal day $fiscaldate"
	java -jar Reporter.jar p=Reporter.properties m=Normal Sales.getReport \
		$vendorId, Sales, Summary, Daily, $fiscaldate
done

# Der Reporter legt die Downloads im Arbeitsverzeichnis komprimiert ab.

# Die komprimierten Reports müssen mit gunzip entpackt werden.

gunzip *.txt.gz

# Das Ergebnis in Form von txt-Dateien muss in den Output-Ordner kopiert werden.
# Diese werden dann in einen lokalen Ordner umgeleitet.

cp *.txt /data/output/ 

# Nun wird eine Zusammenfassung aus allen txt-Dateien generiert.

outputpath=/data/output/summary.tsv

# Dazu wird nur einmal der Header geschrieben.

less *.txt | head -1 > $outputpath
less *.txt | grep -e "^APPLE" >> $outputpath

# Fertig

echo "Saved at $outputpath"

Steht alles, kann man das Docker Image applereporter bauen.

$ docker build --rm -t applereporter .

Ausführung

Ist alles glatt gelaufen, kann man den Container instanzieren. Dabei muss der Output-Ordner in einen lokalen Ordner ~/applereports als "Volume" umgeleitet werden, damit die Reports und die Zusammenfassung nicht verloren gehen. Einen Namen braucht der Container nicht, aber den --rm Parameter, damit Docker nicht mit inaktiven Containern zugemüllt wird.

$ docker run -rm -v ~/applereports:/data/output applereporter

Der Container benötigt eine Weile, bis alle Schleifen durchgelaufen sind. Solange man den Container nicht im Deamon Modus startet kann man der Ausgabe folgen und einfach abwarten, bis das Script fertig ist.

Das Ergebnis ist ein ~/applereports Ordner voll mit Berichten inkl. der Zusammenfassung summary.tsv.

S_D_12345678_20180901.txt
S_D_12345678_20180903.txt
S_D_12345678_20180904.txt
S_D_12345678_20180905.txt
S_D_12345678_20180906.txt
S_D_12345678_20180908.txt
S_D_12345678_20180909.txt
S_D_12345678_20180911.txt
S_D_12345678_20180912.txt
S_D_12345678_20180913.txt
S_D_12345678_20180914.txt
S_D_12345678_20180916.txt
S_D_12345678_20180917.txt
S_D_12345678_20180918.txt
S_D_12345678_20180920.txt
S_D_12345678_20180921.txt
S_D_12345678_20180922.txt
S_D_12345678_20180923.txt
S_D_12345678_20180925.txt
S_M_12345678_201801.txt
S_M_12345678_201802.txt
S_M_12345678_201803.txt
S_M_12345678_201804.txt
S_M_12345678_201805.txt
S_M_12345678_201806.txt
S_M_12345678_201807.txt
S_M_12345678_201808.txt
summary.tsv

Nächsten Schritte

Mit Hilfe der Zusammenfassung kann ich nun mit der Auswertung fortfahren. Auch eine optisch ansprechende Variante für einen Ordner mit steuerrelevanten Akten kann nun erstellt werden.

An dieser Stelle muss ich mir aber zuerst ein paar Gedanken zu den Daten machen.


Häufig gestellte Fragen (FAQ)

Was ist ein AccessToken?

Der AccessToken ersetzt beim Reporter die Username/Passwort Authentifizierung. Der Token ist ein Code, der in iTunes-Connect erstellt wird und der für ca. 6 Monate gültig ist. Sollte der Container also irgendwann einmal seinen Dienst verweigern, ist vermutlich der AccessToken ausgelaufen und muss neu erstellt werden. In der Reporter Dokumentation steht beschrieben, wie man den Token verwalten muss.

Wie lauten meine Accounts?

$ java -jar Reporter.jar p=Reporter.properties m=Robot.XML Sales.getAccounts

Wie komme ich an die Vendor ID?

java -jar Reporter.jar p=Reporter.properties Sales.getVendors

Warum kann man nicht einfach den Jahresbericht herunterladen?

Prinzipiell könnte man ja die Parameter Summary, Yearly, <current year> nutzen, um den Bericht des aktuellen Jahres herunterzuladen. Leider handelt es sich bei diesem Parameter um die fiskalen Jahre und das schließt das laufende Jahr aus. Für das vergangene Jahr ist der Direktdownload jedoch problemlos möglich:

java -jar Reporter.jar p=Reporter.properties m=Robot.XML Sales.getReport 12345678, Sales, Summary, Yearly, 2017

Kann mich ich den Container einloggen und ihn debuggen?

$ docker run -rm -v ~/applereports:/data/output -it applereporter

Es erscheint eine Shell im Container. Um den Container wieder zu verlassen, reicht es einfach das Kommando exit auszuführen.


1 Der Preis für **Argh! Earthlings!** liegt oftmals unter dem eines einzigen Burgers oder Döners (verglichen mit den Preisen gängiger FastFood Restaurants). Zugegeben, **Argh! Earthlings!** schmeckt nicht so vollmundig wie ein Burger oder Döner, aber das Spiel kann man auf seinem Mobilgerät behalten, und das selbst dann, wenn man den Burger bzw. Döner schon längst verputzt hat!