2018. március 2., péntek

Fájl feltöltése Github tárolóba

Egy átlagos napon, egy átlagos feladaton dolgoztam, amikor egy teljesen átlagos, mondhatni triviális dolgot kellett megoldanom. Konkrétan: le kellett tárolni egy állományt egy megadott git tárolóba. Mi sem egyszerűbb, gondoltam naivan, de nem eszik olyan forrón a kását! Nem mennék bele túl mélyen a részletekbe, talán annyi is elég, hogy rendszeresen szoktunk sütni kép-fájlokat különböző felhőkbe. Természetesen az egész sütési folyamat automatizálva van, az eredmény pedig egy katalógusba kerül, ahol a kedves felhasználó ízlése szerint válogathat közülük. Kezdjük hát az egy pont nullával:
git add metadata ; git commit -m"New image available" ; git push

Két probléma is adódott:
  • Egyfelől nagyon ronda, persze működik, és a Jenkins-nek is megvan a joga írni a tárolót, de én valami kifinomultabb megoldásra vágytam.
  • Másfelől pedig maga a sütés egy másik git tárolóból indul ki, szóval vagy valami ideiglenes könyvtárba dolgozok (/tmp, ram disk), aminek az életciklusát kezelni kell, vagy a git tárolóban a git tároló problémájával küzdök, és kényesen ügyelek rá, hogy mindkét tárolóba csak az oda illő dolgok kerüljenek be.
Elvetettem a git parancsok használatát, úgyis Github-on tartjuk a forrást, van annak saját API-ja, megoldom elegánsan a kérdést. A dokumentációt olvasva elég hamar megtaláltam, hogy a v1-es API-ban elérhető direkt fájl feltöltést kompletten kihajították, így a legfrissebb API verzióban nincs ilyenre lehetőség. Persze tudunk fájt feltölteni, viszont ki kell hozzá nyitni a motorháztetőt, azaz a .git könyvtárban kell műveleteket végezni az API segítségével. Lássunk is neki:

#!/bin/bash -e
: ${1?=File name is required}
file_name=$1
GITHUB_ORG=mhmxs
GITHUB_REPO=test-metadata
GITHUB_USER=mhmxs
GITHUB_API_KEY=0ea72b*****************
GITHUB_REPO_URL=https://github.com/$GITHUB_ORG/$GITHUB_REPO
A script első felében a szükséges változókat definiáltam. A GITHUB_API_KEY-nek egy Personal Access Token-t generáltam a Github felületén. Következő lépésben kiderítettem az utolsó commit azonosítóját, és helyét. A JSON válaszok értelmezésére a Jq nevű parancs-soros eszközt választottam.

head=$(curl -sf -u $GITHUB_USER:$GITHUB_API_KEY https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/git/refs/heads/master)
commitSha=$(echo $head | jq -r .object.sha)
commitUrl=$(echo $head | jq -r .object.url)
A git fa szerkezetben tárolja kódunk különböző állapotait, ezért egyel tovább kell mennünk, és meg kell tudnunk, hogy a commit melyik fához tartozik.

treeSha=$(echo $commit | jq -r .tree.sha)
Most, hogy tudjuk az azonosítóját a fának, feltölthetjük az új fájlt, vagyis a blob-ot.

blob=$(curl -sf -u $GITHUB_USER:$GITHUB_API_KEY -X POST https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/git/blobs -d "{\"content\":\"$(base64 $file_name)\",\"encoding\":\"base64\"}")
blobSha=$(echo $blob | jq -r .sha)
Kezd izgalmassá válni a dolog. Ahogy említettem a .git könyvtárban kézi-munkázunk, ami azt jelenti, hogy az imént feltöltött állomány pillanatnyilag sehová sem tartozik, ahhoz ugyanis egy fához kell csatolni azt.

newTree=$(curl -sf -u $GITHUB_USER:$GITHUB_API_KEY -X POST https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/git/trees -d \
"{\"base_tree\":\"$treeSha\",\"tree\":[{\"path\":\"$file_name\",\"mode\":\"100644\",\"type\":\"blob\",\"sha\":\"$blobSha\"}]}")
newTreeSha=$(echo $newTree | jq -r .sha)
Majd készítünk egy új commit-ot, amit összekapcsolunk az utolsó commit-tal, és az imént kreált fára irányítjuk.

newCommit=$(curl -sf -u $GITHUB_USER:$GITHUB_API_KEY -X POST https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/git/commits -d \
"{\"message\":\"Upload $file_name\",\"parents\":[\"$commitSha\"],\"tree\":\"$newTreeSha\"}")
newCommitSha=$(echo $newCommit | jq -r .sha)
Egy utolsó lépés maradt hátra, a master ágon a HEAD címkét át kell helyezni az újdonsült elkészült commit-ra.

curl -s -u $GITHUB_USER:$GITHUB_API_KEY -X PATCH https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/git/refs/heads/master -d "{\"sha\":\"$newCommitSha\",\"force\":true}"
exit 0
Nem állítom, hogy pilóta vizsga kell egy egyszerű fájl feltöltéséhez, de azért nem árt ismerni, hogy miként is működik a git, hogyan tárolja és azonosítja az objektumokat, és nem utolsó sorban miként kezeli a címkéket. A teljes script elérhető itt.