14 czerwca 2023 8 min. czytania

Podejmij akcję i naucz się GitHub Actions!

Programiści uwielbiają automatyzować rzeczy. Ale automatyzacja jest korzystna tylko wtedy, gdy zabiera mniej czasu niż zwraca. Dzięki GitHub Actions może osiągniemy ten zysk.

Zaktualizowano: 14 czerwca 2023
Zużyty, czarno-biały klaps filmowy
Zdjęcie autorstwa Harald Müller

“Nigdy nie poświęcaj 6 minut na zrobienie czegoś ręcznie jeżeli możesz poświęcić 6 godzin na nieudane próby zautomatyzowania tego” - brzmi stary dowcip. Pomimo, że jako programiści mamy tendencję do nadmiernej komplikacji różnych zadań, istnieją oczywiste zalety automatyzacji.

Jak długo możesz pracować nad zwiększeniem wydajności rutynowych zadań, zanim zainwestujesz więcej czasu niż go uzyskasz? - komiks xkcd

Pomyśl o tym - to nie jest tak, że cały dzień poświęcasz na programowanie. Inne żmudne lub organizacyjne zadania również potrzebują naszej atencji: etykietowanie issue, aktualizowanie paczek czy wdrażanie kodu itd. Aaa i nie zapomnijmy o ulubieńcu wszystkich - spotkaniach.

W ciągu ostatniej dekady, programiści stworzyli wiele narzędzi i platform, takich jak CircleCI, Jenkins lub Travis (nie Scott), aby rozwiązać wymienione problemy. My jednak skupimy się na relatywnie nowym graczu w tej grze - GitHub Actions.

Nie kolejne narzędzie CI/CD?

Zanim nauczymy się czym są GitHub Actions, musimy dowiedzieć się czym nie są. “Akcje GitHub to narzędzie CI/CD.” Gdy robiłem research, wiele razy natrafiałem na takie nieporozumienia. I nie zrozum mnie źle - jest nim, ale nie musi być. Przypadek użycia jest szerszy. GitHub Actions to jest platforma do automatyzacji zadań używając zdefiniowanego workflow. CI lub CD jest jednym z wielu przykładów takiego przepływu pracy.

Dlaczego używać Github Actions?

Ok, ale dlaczego użyć ich zamiast innych, zaprawionych w boju rozwiązań? Istnieje kilka powodów.

  • Nie wymagają zewnętrznych integracji. Spoglądając na statystyki, najprawdopodobniej i tak już używasz GitHuba.
  • Konfiguracja jest prosta. Nie musisz być specjalistą DevOps, żeby ich używać (nie złośćcie się na mnie chłopaki - jesteście niezastąpieni przy bardziej złożonych rzeczach).
  • Abstrahują niskopoziomowe komendy i inną logikę. Przypomnij sobie ile małych kroków musisz wykonać, aby uruchomić aplikację node od podstaw. Przypomniałeś sobie? Dobrze, teraz możesz zapomnieć je znowu, bo będą wyabstrahowane.
  • Istnieje wiele integracji z rozmaitymi narzędziami i stosami technologicznymi. Java, .NET, Node.js? Wejdź na GitHub Marketplace i znajdź środowisko, którego potrzebujesz.
  • Nie musisz zaczynać od zera. Wspomniany marketplace oferuje mnóstwo gotowych do użycia aplikacji i akcji. Możesz także tworzyć swoje niestandardowe akcje na bazie szablonów.

Czym jest GitHub workflow?

Na początek zapoznajmy się z podstawową terminologią. Będziemy się uczyć z góry na dół, zaczynając od ogólnych konceptów i przechodząc do detali.

Workflow to zautomatyzowany proces, który uruchamia jedną lub więcej prac (job). Możesz go skonfigurować dodając plik konfiguracyjny YAML w folderze .github/workflows. Składni przyjrzymy się w następnych sekcjach.

Job to zestaw korków definiujący przepływ pracy. Kroki mogą uruchamiać komendy, konfigurować zadania lub wywoływać akcje GitHub. Domyślnie, prace działają równolegle, ale możesz skonfigurować je, aby uruchamiały się sekwencyjnie.

Action to pojedynczy krok wewnątrz pracy. Jest to aplikacja dla platformy GitHub Actions, która wykonuje często powtarzające się zadania. Możesz stworzyć swoje niestandardowe akcje lub znaleźć wiele gotowych do użycia na GitHub Marketplace.

Runner to serwer uruchamiający workflow w odpowiednim momencie. Każda praca w workflow jest uruchamiana w nowym wirtualnym środowisku. GitHub zarządza tymi serwerami i oferuje trzy główne systemy operacyjne: Ubuntu, Windows i macOS.

Jak działają GitHub Actions?

GitHub emituje zdarzenia (events), gdy coś się dzieje “w” lub “w stosunku do” twojego repozytorium - jak np. otworzenie PR lub issue. W odpowiedzi na te zdarzenia, możesz uruchamiać akcje GitHub. Te akcje to są również repozytoria. Ogólnie, przypomina to bazującą na zdarzeniach naturę JavaScriptu. Ogólna idea jest zatem prosta:

  • Nasłuchuj na event
  • Uruchom odpowiedni workflow

Składnia pliku konfigurującego workflow

Tak jak wspomniałem, potrzebujesz pliku YAML, żeby skonfigurować workflow. Przyjrzyjmy się strukturze takiego pliku.

  • name - nazwa workflow. Strona z akcjami w repozytorium ją wyświetli.
  • on - nazwa zdarzenia, które wywołuje workflow, np. push lub pull_request. Istnieje cała lista zdarzeń na stronie GitHub events.
  • jobs - sekcja, gdzie umieszczasz pojedyncze prace.
  • job-name - nazwa zadania, którą możesz edytować. Pod tym kluczem, umieszczasz listę kroków. Każdy krok może:
    • Mieć nazwę - name.
    • Używać zdefiniowanej akcji - uses.
    • Uruchomić komendę w terminalu - run.
    • Być uruchomiony z dodatkowymi parametrami - with.

CI pipeline w GitHub Actions

Ludzie najlepiej uczą się z przykładami (a przynajmniej ja tak mam), więc sprawdźmy przykładową konfigurację ciągłej integracji.

Konfiguracja GitHub Actions CIYAML
1name: CI
2on:
3 pull_request:
4 branches:
5 - main
6 paths:
7 - '**.js'
8 - '**.jsx'
9 - '**.json'
10 - '**.yml'
11
12concurrency:
13 group: ${{ github.ref }}
14 cancel-in-progress: true
15
16jobs:
17 install-cache:
18 runs-on: ubuntu-latest
19 strategy:
20 matrix:
21 node: [16]
22 steps:
23 - name: Checkout commit
24 uses: actions/checkout@v3
25 - name: Use Node ${{ matrix.node }}
26 uses: actions/setup-node@v3
27 with:
28 node-version: ${{ matrix.node }}
29 cache: npm
30 - name: Install dependencies
31 run: npm ci --legacy-peer-deps
32 - name: Cache Cypress binary
33 uses: actions/cache@v3
34 id: cache-cypress
35 with:
36 path: ~/.cache/Cypress
37 key: cypress-binary-${{ hashFiles('**/package-lock.json') }}
38 restore-keys: |
39 cypress-binary-
40 unit-test:
41 runs-on: ubuntu-latest
42 strategy:
43 matrix:
44 node: [16]
45 needs: install-cache
46 steps:
47 - name: Checkout commit
48 uses: actions/checkout@v3
49 - name: Use Node ${{ matrix.node }}
50 uses: actions/setup-node@v3
51 with:
52 node-version: ${{ matrix.node }}
53 cache: npm
54 - name: Install dependencies
55 run: npm ci --legacy-peer-deps
56 - name: Run unit tests
57 run: npm test
58 build:
59 runs-on: ubuntu-latest
60 strategy:
61 matrix:
62 node: [16]
63 needs: unit-test
64 steps:
65 - name: Checkout commit
66 uses: actions/checkout@v3
67 - name: Use Node ${{ matrix.node }}
68 uses: actions/setup-node@v3
69 with:
70 node-version: ${{ matrix.node }}
71 cache: npm
72 - name: Install dependencies
73 run: npm ci --legacy-peer-deps
74 - name: Run build
75 run: npm run build
76 - name: Upload build artifacts
77 uses: actions/upload-artifact@v3
78 with:
79 name: build-output
80 path: |
81 .cache
82 public
83 retention-days: 1
84 e2e-test-chrome:
85 runs-on: ubuntu-latest
86 strategy:
87 matrix:
88 node: [16]
89 needs: build
90 steps:
91 - name: Checkout commit
92 uses: actions/checkout@v3
93 - name: Use Node ${{ matrix.node }}
94 uses: actions/setup-node@v3
95 with:
96 node-version: ${{ matrix.node }}
97 cache: npm
98 - name: Install dependencies
99 run: npm ci --legacy-peer-deps
100 - name: Restore Cypress binary
101 uses: actions/cache@v3
102 id: cache-cypress
103 with:
104 path: ~/.cache/Cypress
105 key: cypress-binary-${{ hashFiles('**/package-lock.json') }}
106 restore-keys: |
107 cypress-binary-
108 - name: Download build artifacts
109 uses: actions/download-artifact@v3
110 with:
111 name: build-output
112 - name: Run Cypress
113 uses: cypress-io/github-action@v4
114 with:
115 start: npm run serve
116 wait-on: 'http://localhost:8000'
117 browser: chrome
118 install: false
119 e2e-tests-firefox:
120 runs-on: ubuntu-latest
121 strategy:
122 matrix:
123 node: [16]
124 needs: build
125 steps:
126 - name: Checkout commit
127 uses: actions/checkout@v3
128 - name: Use Node ${{ matrix.node }}
129 uses: actions/setup-node@v3
130 with:
131 node-version: ${{ matrix.node }}
132 cache: npm
133 - name: Install dependencies
134 run: npm ci --legacy-peer-deps
135 - name: Restore Cypress binary
136 uses: actions/cache@v3
137 id: cache-cypress
138 with:
139 path: ~/.cache/Cypress
140 key: cypress-binary-${{ hashFiles('**/package-lock.json') }}
141 restore-keys: |
142 cypress-binary-
143 - name: Download build artifacts
144 uses: actions/download-artifact@v3
145 with:
146 name: build-output
147 - name: Run Cypress
148 uses: cypress-io/github-action@v4
149 with:
150 start: npm run serve
151 wait-on: 'http://localhost:8000'
152 browser: firefox
153 headed: true
154 install: false

O panie, jaki długi plik! Ale nie bądź przytłoczony - spróbujemy go rozszyfrować. Nasz workflow nazywa się “CI”. Wystartuje, gdy ktoś zrobi PR do gałęzi main. Dodatkowo, zmiany muszą obejmować pliki z wymienionymi rozszerzeniami. Dlaczego? Bo zmiany w plikach takich jak Markdown lub MDX najprawdopodobniej niczego nie zepsują, wiec nie musimy uruchamiać naszego worfklow.

Sekcja concurrency pozwala na anulowanie zadań w trakcie pracy, jeżeli należą do tej samej grupy. Np. kontynuowanie procesu budowania projektu nie ma sensu, jeżeli testy nie przeszły.

Praca install-cache rozpoczyna się typowo - od uzyskania dostępu do repozytorium. Do tego celu istnieje zdefiniowana funkcja, nazwana checkout. Później musimy skonfigurować środowisko node. W naszym przykładzie użyjemy node 16, pobierając liczbę z sekcji matrix. Mając node możemy zainstalować paczki. Komenda npm ci działa podobnie do npm install, ale jest przeznaczona do zautomatyzowanych środowisk. W końcu sprawdzamy czy istnieją jakieś pliki binarne narzędzia Cypress. Pózniej się dowiemy po co.

Praca unit-test, bez niespodzianek, uruchamia testy jednostkowe. Zaczyna się podobnie do poprzedniej pracy - od sprawdzenia plików w cache. Nie musimy instalować zależności z każdym uruchomieniem workflow. Ostatni krok rzeczywiście uruchamia testy.

Kolejna praca buduje projekt. Jest to przykład pracy sekwencyjnej. Ma ona zależność w postaci poprzedniego kroku. Po zbudowaniu projektu, akcja upload-artifact zapisuje w pamięci wyjście tego procesu na jeden dzień.

Dwie ostatnie prace są bardzo podobne - obie uruchamiają testy end-to-end. Jedna z nich w Chrome, druga w Firefox. Te prace muszą zostać uruchomione po fazie budowania. Obie próbują znaleźć zapisane pliki narzędzia Cypress, dla zwiększenia wydajności. Po pomyślnym wykonaniu wszystkich prac, cały workflow jest zakończony.

Strona GitHub Actions ze schematem CI

Mam nadzieję, że ten post był niezłym wprowadzeniem do GitHub Actions. Po jego przeczytaniu powinieneś wiedzieć dlaczego i jak ich używać. Być może będziesz nawet w stanie zautomatyzować jakieś żmudne zadania w mniej niż 6 godzin. Być może będziemy kontynuowali naszą zautomatyzowaną przygodę i napiszemy jakąś niestandardową akcję GitHub. Ale, na razie, the best I can do to zaoferować przydatne linki.

Newsletter, który rozpala ciekawość💡

Subskrybuj mój newsletter, aby otrzymywać comiesięczną dawkę:

  • Nowości, przykładów, inspiracji ze świata front-end, programowania i designu
  • Teorii naukowych i sceptycyzmu
  • Moich ulubionych źródeł, idei, narzędzi i innych interesujących linków
Nie jestem nigeryjskim księciem, aby oferować ci okazje. Nie wysyłam spamu. Anuluj kiedy chcesz.

Pozostań ciekawy. Przeczytaj więcej

Połowa winyla na białym tle1 września 20227 min. czytania

Dostępne animacje w React

Czyli jak nie kręcić swoimi użytkownikami (jak winylem). Niektóre animacje mogą powodować problemy u użytkoników. Zadbamy o nich i sprawimy, że nieistotne animacje będą opcjonalne.

Czytaj wpis
Zdjęcie zmiennych CSS w edytorze Visual Studio Code14 września 20227 min. czytania

Konwertowanie tokenów projektowych na zmienne CSS z Node.js

Konwertowanie tokenów projektowych jest procesem podatnym na błędy - przekonałem się o tym na własnej skórze. Dlatego stworzyłem prosty skrypt dla środowiska Node.js, który pomoże mi z tym zadaniem.

Czytaj wpis
Pięć metalowych kół zębatych na czarnym tle23 września 202210 min. czytania

Gatsby z Netlify CMS

W tym wpisie przyjrzymy się bliżej Netlify CMS. Jest to przykład CMSa nowego typu, który jest oparty o Git. Zintegrujemy go z przykładowym projektem Gatsby.

Czytaj wpis