
Často musím sestavit pipeline pro build projektů v Javě. Někdy je to open source, někdy ne. Nedávno jsem se rozhodl vyzkoušet přesunout část svých repozitářů z Travis-CI a TeamCity na GitHub Actions a zde je, co z toho vzešlo.
Co budeme automatizovat
Nejdříve potřebujeme projekt, který budeme automatizovat, pojďme vytvořit malou aplikaci na Spring Boot / Java 11 / Maven. V rámci tohoto článku nás logika aplikace vůbec nezajímá, důležitá je infrastruktura okolo aplikace, takže nám stačí jednoduchý REST API controller.
Zdroje lze nalézt zde: všechny fáze stavění pipeline jsou zachyceny v pull requestech tohoto projektu.
JIRA a plánování
Je dobré zmínit, že obvykle používáme JIRA jako tracker úloh, takže si vytvoříme samostatnou board pro tento projekt a napíšeme tam první úkoly:

O něco později se ještě vrátíme k tomu, co zajímavého mohou přinést spojení JIRA a GitHub.
Automatizujeme build projektu
Náš testovací projekt se buildí přes Maven, takže jeho build je docela jednoduchý, všechno, co potřebujeme, je mvn clean package.
Abychom to udělali pomocí GitHub Actions, musíme v repozitáři vytvořit soubor s popisem našeho workflow, což můžeme udělat obyčejným yml souborem. Nemohu říct, že mě baví „programování v yml“, ale co se dá dělat — vytvořme v adresáři .github/workflow/ soubor build.yml, ve kterém budeme popisovat kroky při buildu master větve:
name: Build
on:
pull_request:
branches:
- '*'
push:
branches:
- 'master'
jobs:
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- name: set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 1.11
- name: Maven Package
run: mvn -B clean package -DskipTests
on — to je popis události, při které se spustí náš skript.
on: pull_request / push — říká, že tento workflow se má spouštět při každém pushi do masteru a při vytváření pull requestů.
Dále následuje popis úloh (jobs) a kroky provedení (steps) pro každou úlohu.
runs-on — zde si můžeme vybrat cílový operační systém, překvapivě můžeme vybrat i Mac OS, ale u soukromých repozitářů je to docela drahá záležitost (ve srovnání s linuxem).
uses umožňuje znovu použít jiné akce, například pomocí akce actions/setup-java nastavujeme prostředí pro Java 11.
Pomocí with můžeme specifikovat parametry, se kterými spouštíme akci, v podstatě se jedná o argumenty, které budou předány do akce.
Zbývá jen spustit mavenem sestavení projektu: run: mvn -B clean package příznak -B označuje, že potřebujeme non-interactive mode, aby se maven náhodou nezeptal na něco.

Skvěle! Nyní při každém commitu do masteru se spouští sestavení projektu.
Automatizujeme spouštění testů
Sestavení je dobré, ale ve skutečnosti může projekt úspěšně sestavit, ale nemusí fungovat. Proto je dalším krokem potřeba věnovat se automatizaci běhu testů. Navíc je velmi pohodlné sledovat výsledky testů, když provádíte revizi PR - přesně víte, že testy prošly a nikdo nezapomněl, než udělat merge, spustit svou větev.
Spustíme testy při vytváření pull requestu a merge do masteru, zároveň přidáme sestavení zprávy o pokrytí kódu.
name: Build
on:
pull_request:
branches:
- '*'
push:
branches:
- 'master'
jobs:
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- name: nastavit JDK 11
uses: actions/setup-java@v1
with:
java-version: 1.11
- name: Maven Verify
run: mvn -B clean verify
- name: Test Coverage
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
Pro krytí testů používám codecov ve spojení s jacoco pluginem. Codecov má svůj akční, ale potřebuje token pro práci s naším pull requestem:
${{ secrets.CODECOV_TOKEN }} — tuto strukturu se setkáme ještě nejednou, secrets je mechanismus pro ukládání tajemství v GitHubu, můžeme tam zapisovat hesla/tokeny/hosty/url a další data, která bychom neměli odhalovat v kódové základně repozitáře.
Přidání proměnné do secrets lze provést v nastavení repozitáře na GitHubu:

Token lze získat na po autorizaci přes GitHub, pro přidání veřejného projektu stačí projít odkazem ve formátu: /[repo name]. Приватный репозиторий тоже можно добавить, для этого надо дать права codecov приложению в гитхабе.

Přidáváme jacoco plugin do POM souboru:
org.jacoco
jacoco-maven-plugin
0.8.4
prepare-agent
report
test
report
org.apache.maven.plugins
maven-surefire-plugin
2.22.2
plain
**/*Test*.java
**/*IT*.java
Nyní do každého našeho pull requestu vstoupí bot codecov a přidá graf pokrytí:

Přidáme statický analyzátor
Ve většině svých open-source projektech používám SonarCloud pro statickou analýzu kódu, je dost snadné ho připojit k travis-ci. Takže je logické udělat to samé při migraci na GitHub Actions. Marketplace akcí - skvělá věc, ale tentokrát mě trochu zklamal, protože jsem ze zvyku našel potřebnou akci a zapsal ji do workflow. Ukázalo se, že Sonar nepodporuje práci prostřednictvím akce na analýzu projektů na Maven nebo Gradle. O tom je samozřejmě napsáno v dokumentaci, ale kdo ji čte?!
Přes akci to nejde, takže to uděláme přes mvn plugin:
name: SonarCloud
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened]
jobs:
sonarcloud:
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v1
- name: Nastavit JDK
uses: actions/setup-java@v1
with:
java-version: 1.11
- name: Analyzovat s SonarCloud
# nastavit proměnné prostředí:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# spustit Sonar Maven plugin:
run: mvn -B verify sonar:sonar -Dsonar.projectKey=antkorwin_github-actions -Dsonar.organization=antkorwin-github -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=$SONAR_TOKEN -Dsonar.coverage.jacoco.xmlReportPaths=./target/site/jacoco/jacoco.xml
SONAR_TOKEN — lze získat na a musí být zapsán v secrets. GITHUB_TOKEN — je integrovaný token, který generuje GitHub, s jehož pomocí se sonarcloud[bot] může autorizovat v gitu, aby nám zanechal zprávy v pull requestech.
Dsonar.projectKey — název projektu v Sonaru, lze ho nalézt v nastavení projektu.
Dsonar.organization — název organizace z GitHubu.
Vytvoříme pull request a počkáme, až sonarcloud[bot] přijde s komentáři:

Správa vydání
Build jsme nastavili, testy proběhly, můžeme udělat i release. Podívejme se, jak GitHub Actions výrazně zjednodušuje správu vydání.
V práci mám projekty, jejichž kódová základna leží v bitbucketu (všechno jako v tom příběhu „během dne píšu do bitbucketu, v noci commitnu na GitHub“). Bohužel v bitbucketu nejsou vestavěné nástroje pro správu releasů. To je problém, protože pro každé vydání musím ručně vytvořit stránku v confluence a vložit tam všechny funkce, které do vydání patří, prohledávat černé myšlení, úkoly v jira, commity v repozitáři. Je tu mnoho možností, jak se splést, něco zapomenout nebo napsat, co jsme již vydali minule, někdy vůbec není jasné, k čemu patří nějaký pull request — je to funkce nebo oprava chyb, nebo úprava testů, nebo něco infrastrukturního.
Jak nám může pomoci GitHub actions? Existuje skvělý action — release drafter, který umožňuje nastavit šablonu souboru release notes pro kategorii pull requestů a automaticky je seskupovat do souboru release notes:

Příklad šablony pro nastavení zprávy (.github/release-drafter.yml):
name-template: 'v$NEXT_PATCH_VERSION'
tag-template: 'v$NEXT_PATCH_VERSION'
categories:
- title: ' Nové funkce'
labels:
- 'type:features'
# do této kategorie shromažďujeme všechny PR s označením type:features
- title: ' Opravy chyb'
labels:
- 'type:fix'
# analogicky pro označení type:fix a podobně.
- title: ' Dokumentace'
labels:
- 'type:documentation'
- title: ' Konfigurace'
labels:
- 'type:config'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
template: |
## Změny
$CHANGES
Přidáváme skript pro generování návrhu vydání (.github/workflows/release-draft.yml):
name: "Vytvořit návrh vydání"
on:
push:
branches:
- master
jobs:
update_draft_release:
runs-on: ubuntu-18.04
steps:
- uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Od této chvíle budou všechny pull requesty automaticky shromažďovány do release notes — kouzlo!
Může nastat otázka: co když si vývojáři zapomenou nastavit označení v PR? Pak není jasné, do které kategorie ho zařadit, a znovu bude třeba se tím zabývat ručně, s každým PR zvlášť. Abychom tuto situaci vyřešili, můžeme použít další akcí — label verifier — ten kontroluje přítomnost tagů na pull requestu. Pokud chybí jakýkoli povinný tag, test bude neúspěšný a tuto zprávu uvidíme v našem pull requestu.
name: "Ověřit typové štítky"
on:
pull_request:
types: [opened, labeled, unlabeled, synchronize]
jobs:
triage:
runs-on: ubuntu-18.04
steps:
- uses: zwaldowski/match-label-action@v2
with:
allowed: 'type:fix, type:features, type:documentation, type:tests, type:config'
Nyní je každý pull request třeba označit jedním z tagů: type:fix, type:features, type:documentation, type:tests, type:config.

Automatické anotování pull requestů
Jakmile jsme se dotkli takového tématu, jako je efektivní práce s pull requesty, je dobré zmínit také akci, jako je labeler, který přiřazuje štítky do PR na základě toho, jaké soubory byly změněny. Například můžeme označit jako [build] jakýkoli pull request, v němž došlo ke změnám v adresáři .github/workflow.
Jeho připojení je docela jednoduché:
name: "Automatické přiřazení témat k PR"
on:
- pull_request
jobs:
triage:
runs-on: ubuntu-18.04
steps:
- uses: actions/labeler@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
Budeme také potřebovat soubor s popisem odpovídajících adresářů projektu a témat pull requestů:
theme:build:
- ".github/**"
- "pom.xml"
- ".travis.yml"
- ".gitignore"
- "Dockerfile"
theme:code:
- "src/main/*"
theme:tests:
- "src/test/*"
theme:documentation:
- "docs/**"
theme:TRASH:
- ".idea/**"
- "target/**"
Nepodařilo se mi spojit akci, která automaticky přiřazuje štítky do pull requestů, s akcí, která kontroluje přítomnost povinných štítků; match-label odmítá vidět štítky přiřazené botem. Zdá se, že je jednodušší napsat vlastní akci, která kombinuje oba kroky. Ale i v takovém stavu se to docela pohodlně používá, je třeba vybrat štítek ze seznamu při vytváření pull requestu.
Je čas na nasazení

Zkusil jsem několik variant nasazení přes GitHub Actions (přes ssh, přes scp a pomocí docker-hub) a mohu říct, že pravděpodobně najdete způsob, jak nahrát binárku na server, jakkoli složitý je váš pipeline.
Líbí se mi možnost mít celou infrastrukturu na jednom místě, proto se podíváme, jak udělat nasazení v GitHub Packages (to je repozitář pro binární obsah, npm, jar, docker).
Skript pro sestavení docker obrazu a jeho publikaci v GitHub Packages:
název: Nasazení docker obrazu
na:
push:
větve:
- 'master'
úkoly:
build_docker_image:
běží na: ubuntu-18.04
kroky:
# Vytvoření JAR:
- používá: actions/checkout@v1
- název: nastavit JDK 11
používá: actions/setup-java@v1
s:
java-verze: 1.11
- název: Maven Package
spusť: mvn -B clean compile package -DskipTests
# Nastavení globálních proměnných prostředí:
- název: nastavit globální env
id: global_env
spusť: |
echo "::set-output name=IMAGE_NAME::${GITHUB_REPOSITORY#*\/}"
echo "::set-output name=DOCKERHUB_IMAGE_NAME::docker.pkg.github.com\/${GITHUB_REPOSITORY}\/${GITHUB_REPOSITORY#*\/}"
# Vytvoření Docker obrazu:
- název: Vytvořit a označit obraz
spusť: |
docker build -t "${{ steps.global_env.outputs.DOCKERHUB_IMAGE_NAME }}:latest" -t "${{ steps.global_env.outputs.DOCKERHUB_IMAGE_NAME }}:${GITHUB_SHA::8}" .
- název: Docker přihlášení
spusť: docker login docker.pkg.github.com -u $GITHUB_ACTOR -p ${{secrets.GITHUB_TOKEN}}
# Publikování obrazu do github package repository:
- název: Publikovat obraz
env:
IMAGE_NAME: $GITHUB_REPOSITORY
spusť: docker push "docker.pkg.github.com\/$GITHUB_REPOSITORY\/${{ steps.global_env.outputs.IMAGE_NAME }}"
Nejprve musíme sestavit JAR soubor naší aplikace, po čemž vypočítáme cestu k GitHub docker registry a název našeho obrazu. Existuje zde několik triků, se kterými jsme se dosud nesetkali:
- konstruce ve tvaru: echo «::set-output name=NAME::VALUE» umožňuje nastavit hodnotu proměnné v aktuálním kroku, aby ji bylo možné později číst ve všech ostatních krocích.
- Získat hodnotu proměnné nastavené v předchozím kroku lze prostřednictvím identifikátoru tohoto kroku: ${{ steps.global_env.outputs.DOCKERHUB_IMAGE_NAME }}
- Ve standardní proměnné GITHUB_REPOSITORY je uloženo názvosloví repozitáře a jeho vlastník („owner/repo-name“). Abychom z tohoto řetězce vystřihli všechno kromě názvu repozitáře, použijeme bash syntaxi: ${GITHUB_REPOSITORY#*\/}
Dále potřebujeme sestavit docker obraz:
docker build -t "docker.pkg.github.com\/antkorwin\/github-actions\/github-actions:latest"
Přihlásit se do registry:
docker login docker.pkg.github.com -u $GITHUB_ACTOR -p ${{secrets.GITHUB_TOKEN}}
A publikovat obraz v GitHub Packages Repository:
docker push "docker.pkg.github.com\/antkorwin\/github-actions\/github-actions"
Abychom určili verzi obrazu, používáme první číslice ze SHA-hashe commitu — GITHUB_SHA zde také má svá úskalí, pokud provádíte takové sestavení nejen při merge do masteru, ale také na základě události vytváření pull requestu, může se SHA neshodovat s hashem, který vidíme v historii gitu, protože akce actions/checkout vytváří svůj unikátní hash, aby se zabránilo vzájemným blokacím akcí v PR.

Pokud vše proběhlo úspěšně, otevřením oddílu packages (https://github.com/antkorwin/github-actions/packages) v repozitáři uvidíte nový docker obraz:

Zde si můžete prohlédnout seznam verzí dockerového obrazu.
Teď už zbývá jen nakonfigurovat náš server pro práci s tímto registry a restartovat službu. Jak to udělat pomocí systemd, to pravděpodobně popíšu jindy.
Monitoring
Podívejme se na jednoduchou variantu, jak provádět kontrolu zdraví naší aplikace pomocí GitHub Actions. V naší bootovací aplikaci je actuator, takže API pro kontrolu jejího stavu nemusíme ani psát, pro lenochy je už vše hotovo. Stačí jen zavolat host: SERVER-URL:PORT/actuator/health
$ curl -v 127.0.0.1:8080/actuator/health
> GET /actuator/health HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.61.1
> Accept: */*
< HTTP/1.1 200
< Content-Type: application/vnd.spring-boot.actuator.v3+json
< Transfer-Encoding: chunked
< Date: Thu, 04 Jun 2020 12:33:37 GMT
{"status":"UP"}
Všechno, co potřebujeme, je napsat úlohu pro kontrolu serveru pomocí cron, a pokud neodpoví, pošleme oznámení do Telegramu.
Nejprve se podíváme, jak spustit workflow pomocí cron:
on:
schedule:
- cron: '*/5 * * * *'
Je to jednoduché, ani se nechce věřit, že na GitHubu lze udělat takové události, které se vůbec nehodí do webhooků. Podrobnosti najdete v dokumentaci:
Kontrolu stavu serveru provedeme ručně pomocí curl:
jobs:
ping:
runs-on: ubuntu-18.04
steps:
- name: curl actuator
id: ping
run: |
echo "::set-output name=status::$(curl ${{secrets.SERVER_HOST}}/api/actuator/health)"
- name: health check
run: |
if [[ ${{ steps.ping.outputs.status }} != *"UP"* ]]; then
echo "health check is failed"
exit 1
fi
echo "It's OK"
Nejprve uložíme do proměnné, co server odpověděl na požadavek, a v dalším kroku zkontrolujeme, že status je UP, a pokud tomu tak není, tak vystoupíme s chybou. Pokud je třeba ručně "zhodit" akci, pak exit 1 — je to vhodná zbraň.
- name: send alert in telegram
if: ${{ failure() }}
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_TO }}
token: ${{ secrets.TELEGRAM_TOKEN }}
message: |
Health check of the:
${{secrets.SERVER_HOST}}/api/actuator/health
failed with the result:
${{ steps.ping.outputs.status }}
Odeslání do Telegramu provedeme pouze v případě, že akce selhala v předchozím kroku. Pro odeslání zprávy používáme appleboy/telegram-action, jak získat token bota a id chatu si můžete přečíst v dokumentaci:

Nezapomeňte uvést v tajemstvích na GitHubu: URL serveru a tokeny pro Telegram bota.
Bonusový trénink — JIRA pro lenochy
Slíbil jsem, že se vrátíme k JIRA, a jsme zpět. Stokrát jsem na standupech viděl situaci, kdy vývojáři dokončili funkci, sloučili větev, ale zapomněli přesunout úkol do JIRA. Samozřejmě, kdyby se to vše dělalo na jednom místě, bylo by to jednodušší, ale ve skutečnosti píšeme kód v IDE, sloučíme větve v bitbucketu nebo GitHubu, a úkoly pak taháme do JIRA, k tomu je potřeba otevírat nová okna, někdy se znovu přihlásit atd. Když přesně víte, co máte dělat dál, nemá smysl zbytečně otevírat desku. Nakonec musíme ráno na standupu utrácet čas na aktualizaci úkolové desky.
GitHub nám pomůže i s touto rutinní činností, nejprve můžeme automaticky přesunout úkoly do sloupce code_review, když přidáme pull request. Vše, co potřebujeme, je dodržovat dohodu o pojmenování větví:
[název projektu]-[číslo úkolu]-název
například, pokud je klíč projektu „GitHub Actions“ GA, pak GA-8-jira-bot může být větví pro realizaci úkolu GA-8.
Integrace s JIRA funguje přes akce od Atlassian, nejsou ideální, musím říct, že některé z nich mi vůbec nefungovaly. Ale budeme diskutovat pouze o těch, které určitě fungují a jsou aktivně používány.
Nejprve se musíme přihlásit do JIRA pomocí akce: atlassian/gajira-login
jobs:
build:
runs-on: ubuntu-latest
name: Jira Workflow
steps:
- name: Login
uses: atlassian/gajira-login@master
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
K tomu musíme získat token v JIRA, jak na to, je popsáno zde:
Vytáhneme identifikátor úkolu z názvu větve:
- name: Find Issue
id: find_issue
shell: bash
run: |
echo "::set-output name=ISSUE_ID::$(echo ${GITHUB_HEAD_REF} | egrep -o 'GA-[0-9]{1,4}')"
echo název větve: $GITHUB_HEAD_REF
echo extrahovaný úkol: ${GITHUB_HEAD_REF} | egrep -o 'GA-[0-9]{1,4}'
- name: Check Issue
shell: bash
run: |
if [[ "${{steps.find_issue.outputs.ISSUE_ID}}" == "" ]]; then
echo "Prosím, pojmenujte svou větev podle úkolu JIRA: [project_key]-[task_number]-branch_name"
exit 1
fi
echo úspěšně nalezený úkol JIRA: ${{steps.find_issue.outputs.ISSUE_ID}}
Pokud hledáte na GitHub marketplace, můžete najít akci pro tento úkol, ale musel jsem napsat to samé pomocí grep podle názvu větve, protože tato akce od Atlassian nechtěla fungovat na mém projektu, zkoumat, co je špatně, trvá déle než to udělat ručně.
Stačí přesunout úkol do sloupce „Code review“ při vytváření pull requestu:
- name: Přechod úkolu
if: ${{ success() }}
uses: atlassian/gajira-transition@master
with:
issue: ${{ steps.find_issue.outputs.ISSUE_ID }}
transition: "Code review"
Pro to existuje speciální akce na GitHubu, všechno, co potřebuje, je identifikátor úkolu, získaný v předchozím kroku, a autorizace v JIRA, kterou jsme provedli výše.

Podobně lze přetahovat úkoly při merge do masteru a dalších událostech z GitHub workflow. Obecně to závisí na vaší fantasii a přání automatizovat rutinní procesy.
Závěry
Pokud se podíváme na klasickou diagram DEVOPS, pokryli jsme všechny fáze, kromě operace, myslím, že pokud se budeme snažit, můžeme najít nějakou akci v marketu pro integraci s help-desk systémem, takže to vezmeme jako základní a na základě jejího použití můžeme učinit závěry.

Plusy:
- Marketplace s hotovými akcemi na každou příležitost je opravdu skvělý. U většiny z nich si také můžete prohlédnout zdrojový kód, abyste pochopili, jak vyřešit podobný problém, nebo rovnou poslat požadavek na funkci autorovi přímo v repozitáři na Gitu.
- Výběr cílové platformy pro sestavení: Linux, macOS, Windows je docela zajímavá vlastnost.
- GitHub Packages je skvělá věc, mít celou infrastrukturu na jednom místě je pohodlné, není třeba procházet různá okna, vše je na dosah jednoho či dvou kliknutí myši a skvěle integrováno s GitHub Actions. Podpora docker registry v bezplatné verzi je také dobré plus.
- GitHub skrývá tajemství v logech sestavení, takže jeho používání pro uchovávání hesel a tokenů není tak děsivé. Během všech experimentů se mi ještě nikdy nepodařilo vidět tajemství v čisté podobě v konzoli.
- Je bezplatný pro projekty Open Source.
Nevýhody:
- YML, prostě ho nemám rád. Při práci s tímto procesem je moje nejčastější zpráva o commitu „oprava formátu yml“, občas zapomenu dát tabulátor nebo napíšu na špatný řádek. Obecně sedět před obrazovkou s úhloměrem a pravítkem není příjemná činnost.
- DEBUG, ladit proces commity, spuštěním nové sestavy a výstupem do konzole není vždy pohodlné, ale to je spíše z kategorie „už jste rozmazlení“, zvyklí pracovat s pohodlnými IDE, kde lze ladit cokoliv.
- Svůj akční kód můžete napsat na čemkoli, pokud ho zabalíte do Dockeru, ale nativně je podporován pouze JavaScript. Samozřejmě, záleží na vkusu, ale já bych preferoval něco jiného místo JS.
Připomínám, že repozitář se všemi skripty je zde:
Příští týden budu vystupovat na konferenci Heisenbug 2020 Piter. Nejenže se podělím o to, jak se vyhnout chybám při přípravě testovacích dat, ale také se podělím o své triky při práci s datovými sadami v Java aplikacích!
Zdroj: habr.com
