Jak zrobić własną GitHub Action?
Cześć!
Jak pewnie wiesz, jestem fanem dokumentacji. Kilka praktycznych rad na ten temat opisałem na blogu:
- “How to create projections of events for nested object structures?” - https://event-driven.io/en/how_to_create_projections_of_events_for_nested_object_structures/
- “Architect Manifesto” https://event-driven.io/en/architect_manifesto/.
Uważam, że dobra dokumentacja, to taka, która jest dokumentacją żyjącą razem z projektem. Tworzenie dokumentacji ma sens, gdy odzwierciedla ona bieżący stan kodu. Nie ma nic gorszego niż nieaktualna dokumentacja. Jeśli i tak musimy zaglądać do kodu, to jest to strata czasu.
Dzisiaj chciałbym pokazać trochę kuchni. Eufemizmem jest powiedzenie, że dokumentacja EventStoreDB nie była najlepsza. Mozolną pracą powoli docieramy do miejsca, gdzie można powiedzieć, że dokumentacja “jest OK”. EventStoreDB jest sporym projektem, dodatkowo wewnętrznie rozbudowanym. Przy każdej zmianie trzeba wziąć pod uwagę wiele przypadków. Do tej pory dokumentacja powstawała już po tym jak funkcjonalność została dodana. Programiści wrzucali krótką informację do pliku Changelog. Potem ktoś z naszego zespołu DevAdvocacy to opisywał. Nie było to idealne, bo nie zawsze się wyrabialiśmy. Dodatkowo nie wiedzieliśmy wszystkich szczegółów implementacji bez dopytywania czy analizy kodu. Dodatkowo silne skupienie na dokumentacji, zabiera nam czas na inne inicjatywy (np. lepsze sample, materiały itd.). Dlatego też krokiem, który chcieliśmy zrobićbyło przeniesienie dokumentacji do repozytoriów z kodem. Dzięki temu mogą one zostać włączone w normalny proces Code Review.
Mieliśmy już wstępnie skonfigurowane kopiowanie dokumentacji z innych repozytoriów (https://github.com/EventStore/documentation/blob/master/import/import-client-docs.js). Nie było to jednak takie trywialne, jak mogłoby się wydawać. Sporo rzeczy trzeba było ujednolicić i wziąć pod uwagę. Na przykład zgodność URL z tym jakie były wcześniej, żeby:
- SEO się zgadzało.
- Zachomikowane zakładki dalej się otwierały.
- Wewnętrzne linkowanie działało.
Dokumentacja dla różnych wersji EventStoreDB się różni. Jeślibyśmy chcieli, żeby dokumentacja była gdzie kod, to jeszcze trzeba było ją umieścić na odpowiednich release branchach. Rodzi to pewne trudności, bo nasza dokumentacja (mimo znaczącej poprawy) ma ciągle braki. Jeszcze nie mamy tego komfortu, żeby dokumentować tylko najnowszą wersję. Często dokumentujemy rzeczy, które dotykają kilka wersji.
Uważam, że przy mechanicznej powtarzalnej pracy najłatwiej o głupie błędy. Taką sytuacją byłaby konieczność pamiętania o robieniu cherry-picków pomiędzy kilkoma branchami. Dodatkowo wystawienie pull requestów itd. Irytuje mnie, gdy ktoś mówi “przecież to proste, programista powinien o tym pamiętać”. Może i proste, może i mógłby o tym pamiętać, ale po co? Jeżeli zsumujemy sobie takie sytuacje, gdzie musimy o czymś pamiętać, to wychodzi, że w pracy nic innego nie robimy niż przechodzenie w głowie checklisty. Nudne, powtarzalne sytuacje trzeba automatyzować.
Dlatego zdecydowaliśmy się na wprowadzenie automatyzacji. Dodałem w zeszłym tygodniu własną akcje GitHub, która sama zrobi cherry-pick zmian z zamkniętego pull request. Dla każdej labelki cherry-pick:{targetbranch}_ nadanej naszemu pull requestowi, zrobi osobny PR z kopią naszych zmian.
GitHub umożliwia zrobienie własnych akcji przy pomocy Docker lub JavaScript. Ja użyłem tego drugiego podejścia. Sam kod akcji jest względnie prosty, ale ile przekleństw i nerwów mnie to kosztowało - to moje. Tak jak GitHub actions mają spore możliwości, tak mają tragiczną dokumentację. Stąd też mój pomysł, żeby się podzielić z Wami tym przykładem.
Własną akcję możemy zdefiniować w repozytorium, gdzie mamy kod. Można też zrobić zbiorcze repozytorium, które będzie przechowywało akcje wspólne. Wybraliśmy te drugie rozwiązanie, gdyż:
- łatwiej je utrzymać.
- Zmian wrzucone do niego z automatu będą widoczne przy kolejnym uruchomieniu akcji w innym repozytorium. Nie potrzeba dodatkowych zmian itd.
- Można mieć kod wspólny używany pomiędzy różnymi akcjami.
Jakie bolączki napotkałem poza kiepską dokumentacją? Np. jeśli robimy akcję przy pomocy JavaScript, to musimy trzymać w naszym repozytorium node_modules. Aktualnie GitHub przy uruchomieniu pipeline nie zainstaluje sam zależności. Ma to sens, bo mamy te same zależności w każdym wywołaniu. Niemniej, jest przede wszystkim upierdliwe. Jak wiadomo, node_modules mogą zajmować dużo miejsca. Musimy narzucić sobie rygor pilnowania ich wagi. Dodatkowo nasze reguły ignorowania plików w Git mogą nam spłatać psikusa. Ja np. straciłem dużo czasu, gdy okazało się, że reguła ignorowała wrzucenie plików do jednego pakietu. Jedna z używanych przeze mnie bibliotek miał w sobie zależność do pakietu NPM o nazwie debug. Kurtyna.
Kod akcji wraz jest publicznie dostępny tutaj:
- README - https://github.com/EventStore/Automations/tree/master/cherry-pick-pr-for-label
- kod - https://github.com/EventStore/Automations/blob/master/cherry-pick-pr-for-label/index.js
- współdzielony kod - https://github.com/EventStore/Automations/tree/master/lib
Jak widać, kod jest względnie prosty. Używa SDK udostępnionego z GitHub o nazwie Octokit (https://docs.github.com/en/rest/overview/libraries). Jestem całkiem zadowolony z tej akcji, bo oprócz samej logiki też potrafi komentować i informować o statusie. Tak, aby jeszcze ułatwić proces. Przykładowy komentarz zobacz tutaj: https://github.com/EventStore/EventStore/pull/2903#pullrequestreview-633505402.
Jak użyć tej akcji?
Wystarczy zdefiniować workflow we właściwym repo (https://github.com/EventStore/EventStore/blob/master/.github/workflows/cherry-pick-pr-for-label.yml):
name: Cherry pick PR commits for label
on:
# można by użyć pull_request, ale...
# pull_request_target odpala z uprawnieniami osoby "merdżującej"
# co jest kluczowe przy użyciu fork
pull_request_target:
types: [closed]
jobs:
cherry_pick_pr_for_label:
name: Cherry pick PR commits for label
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cherry Pick PR for label
# tutaj podajesz, z którego:
# - repozytorium: EventStore/Automations
# - ścieżki: /cherry-pick-pr-for-label
# - brancha: @master
uses: EventStore/Automations/cherry-pick-pr-for-label@master
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Co ciekawe. Nie znalazłem tego nigdzie w dokumentacji…
Co jeszcze ciekawsze: możesz użyć tej akcji w swoim repozytorium. Na przykład do synchronizacji kodu pomiędzy branchami. Oczywiście długo żyjące gałęzie kodu to zmora. Jednak, wiadomo jak jest: czasem się bez nich nie obejdzie.
Własne GitHub actions dają bardzo dużo możliwości. Można tworzyć bardzo ciekawe automatyzacje. Trzeba przegryźć się przez te pierwsze problemy. Mam nadzieję, że dzięki temu mailowi i tym przykładom będzie Ci łatwiej. Zachęcam do zabawy z tym kodem. Daj znać, jakie widzisz potencjalne zastosowania siebie.
Pozdrawiam!
Oskar
P.S. W zeszłym tygodniu napisałem na blogu o tym, jak tworzyć projekcje zdarzeń dla bardziej rozbudowanych obiektów (np. ze strukturą hierarchiczną): https://event-driven.io/en/how_to_create_projections_of_events_for_nested_object_structures/. Zachęcam do lektury!
A oprócz tego, jak co tydzień nowe Architecture Weekly: https://github.com/oskardudycz/ArchitectureWeekly/discussions/16