
Pogosto moram zgraditi pipeline za gradnjo projektov v Javi. Včasih so to odprtokodni projekti, včasih pa ne. Nedavno sem se odločil preseliti nekatere svoje repozitorije iz Travis-CI in TeamCity na GitHub Actions, in to, kar je iz tega nastalo.
Kaj bomo avtomatizirali
Za začetek potrebujemo projekt, ki ga bomo avtomatizirali, naredimo majhno aplikacijo na Spring Boot / Java 11 / Maven. V okviru tega članka nas ne zanima logika aplikacije, pomembna je infrastruktura okrog aplikacije, zato nam bo zadostoval preprost REST API kontroler.
Izvorno kodo lahko vidite tukaj: vsi koraki za gradnjo pipeline-a so prikazani v pull requestih tega projekta.
JIRA in načrtovanje
Omeniti je treba, da običajno uporabljamo JIRA kot sledilnik nalog, zato bomo ustvarili posebno tablo za ta projekt in nanjo dodali prve naloge:

Kasneje se bomo še vrnili k temu, kar zanimivega lahko prinese povezava JIRA in GitHub.
Avtomatiziramo gradnjo projekta
Naš testni projekt se gradi preko maven, zato je njegova gradnja precej preprosta, vse, kar potrebujemo, je mvn clean package.
Da bi to naredili s pomočjo GitHub Actions, bomo morali v repozitoriju ustvariti datoteko z opisom našega delovnega procesa, to lahko storimo z navadno yml-datoteko, ne morem reči, da imam rad 'programiranje v yml', toda kaj naj storimo — v direktoriju .github/workflow/ ustvarimo datoteko build.yml, v kateri bomo opisali dejanja pri gradnji glavne veje:
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 opis dogodka, ob katerem se naš skript zažene.
on: pull_request / push — pove, da je treba ta delovni postopek zagnati ob vsakem pushu v glavno vejo in ob ustvarjanju pull requestov.
Nato sledi opis nalog (jobs) in korakov izvajanja (steps) za vsako nalogo.
runs-on — tukaj lahko izberemo ciljno operacijski sistem, kar je presenetljivo, saj lahko izberemo celo Mac OS, vendar je to na zasebnih repozitorijih precej draga izkušnja (v primerjavi z linuxom).
uses omogoča ponovno uporabo drugih akcij, tako da z akcijo actions/setup-java nastavimo okolje za Java 11.
S pomočjo with Lahko določimo parametre, s katerimi zaženemo dejanje, v bistvu so to argumenti, ki jih bomo posredovali akciji.
Ostane le, da z Mavenom zaženemo gradnjo projekta: run: mvn -B clean package zastavico -B to pomeni, da potrebujemo način non-interactive, da Maven ne bi nenadoma želel nekaj vprašati.

Odlično! Zdaj se ob vsakem commitu v master zažene gradnja projekta.
Avtomatiziramo zagon testov.
Gradnja je dobra, a v resnici se lahko projekt uspešno gradi, vendar ne deluje. Zato se moramo naslednji korak posvetiti avtomatizaciji izvajanja testov. Poleg tega je precej priročno videti rezultate prehoda testov, ko opravljate pregled PR — točno veste, da testi uspejo in nihče ni pozabil, preden izvede merge, preizkusiti svojo vejo.
Izvajamo teste ob ustvarjanju pull requesta in merge v master ter dodajamo gradnjo poročila o pokritosti s kodo.
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 Verify
run: mvn -B clean verify
- name: Test Coverage
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
Za pokritost testov uporabljam codecov skupaj z jacoco vtičnikom. Codecov ima svojo akcijo, vendar potrebuje za delo s našim pull requestom žeton:
${{ secrets.CODECOV_TOKEN }} — to konstrukcijo bomo srečali še večkrat, secrets je mehanizem za shranjevanje skrivnosti v GitHubu, kjer lahko zapišemo gesla/žetone/gostitelje/url-je in druge podatke, ki jih ne smemo razkrivati v kodo repozitorija.
Dodajanje spremenljivke v secrets je mogoče v nastavitvah repozitorija na GitHubu:

Žeton lahko pridobite na po prijavi prek GitHuba, za dodajanje javnega projekta preprosto sledite povezavi, kot je: /[repo name]. Приватный репозиторий тоже можно добавить, для этого надо дать права codecov приложению в гитхабе.

Dodajamo jacoco vtičnik v POM datoteko:
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
Zdaj bo v vsak naš pull request vstopil bot codecov in dodal grafikon spremembe pokritosti:

Dodamo statični analizator
V večini svojih odprtokodnih projektov uporabljam sonar cloud za statični analizator kode, povezati ga je precej enostavno s travis-ci. Tako da je to logičen korak pri migraciji na GitHub Actions, da storimo enako. Market akcij — super stvar, vendar je tokrat nekoliko razočaral, ker sem po navadi našel potrebno akcijo in jo vključil v delovni tok. Ugotovil pa sem, da sonar ne podpira dela prek akcije za analizo projektov na maven ali gradle. O tem je seveda napisano v dokumentaciji, vendar kdo pa jo bere?!
Preko akcije ne gre, zato bomo to naredili preko mvn vtičnika:
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: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 1.11
- name: Analyze with SonarCloud
# set environment variables:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# run 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 — ga lahko pridobimo na in ga je potrebno vpisati v secrets. GITHUB_TOKEN — to je vgrajen token, ki ga generira GitHub, s katerim se sonarcloud[bot] lahko avtentificira v Gitu, da nam pusti sporočila v pull requestih.
Dsonar.projectKey — ime projekta v sonaru, si ga lahko ogledate v nastavitvah projekta.
Dsonar.organization — ime organizacije iz GitHuba.
Naredimo pull request in počakamo, da sonarcloud[bot] pride v komentarjih:

Upravljanje izdaj
Gradnjo smo nastavili, teste smo opravili, lahko pa naredimo tudi izdajo. Poglejmo, kako GitHub Actions znatno poenostavi upravljanje z izdaji.
Na delu imam projekte, katerih koda leži v bitbucket-u (vse kot v tisti zgodbi »čez dan pišem v bitbucket, ponoči committam v GitHub«). Na žalost v bitbucket-u ni vgrajenih orodij za upravljanje izdaj. To je težava, saj moramo za vsak izdelek ročno ustvariti stran v confluence in tja dodati vse funkcionalnosti, vključene v izdelek, pregledati misli, naloge v jira, commit-e v repozitoriju. Možnosti za napake je veliko, lahko nekaj pozabimo ali zapišemo tisto, kar smo že izdali prejšnjič, včasih pa je preprosto nejasno, kam uvrstiti določen pull-request — je to funkcionalnost ali popravilo napak, ali sprememba testov, ali kaj infrastrukturnega.
Kako nam lahko pomaga GitHub actions? Obstaja odličen action — release drafter, ki omogoča nastavitev predloge datoteke zapiskov o izdaji, da konfiguriramo kategorije pull-request-ov in jih samodejno združujemo v datoteko z zapiski o izdaji:

Primer predloge za nastavitev poročila (.github/release-drafter.yml):
name-template: 'v$NEXT_PATCH_VERSION'
tag-template: 'v$NEXT_PATCH_VERSION'
categories:
- title: ' Nove funkcionalnosti'
labels:
- 'type:features'
# v to kategorijo zbiramo vse PR z oznako type:features
- title: ' Popravila napak'
labels:
- 'type:fix'
# podobno za oznako type:fix itd.
- title: ' Dokumentacija'
labels:
- 'type:documentation'
- title: ' Konfiguracija'
labels:
- 'type:config'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
template: |
## Spremembe
$CHANGES
Dodajamo skripto za generiranje osnutka izdaje (.github/workflows/release-draft.yml):
name: "Ustvari osnutek izdaje"
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 }}
Vsi pull-request-i od tega trenutka naprej se bodo samodejno zbirali v zapiskih o izdaji — čarobno!
Tu se lahko pojavi vprašanje: kaj se zgodi, če razvijalci pozabijo dodeliti oznake v PR? Takrat ni jasno, v katero kategorijo ga uvrstiti, in spet bo treba raziskovati ročno, pri vsakem PR posebej. Da bi odpravili to težavo, lahko uporabimo še en action — label verifier — ki preverja prisotnost oznak na pull-request-u. Če ni nobene obvezne oznake, bo preverjanje spodletelo in obvestilo o tem bomo videli v našem pull-request-u.
name: "Preveri oznake tipa"
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'
Zdaj je treba vsak pull-request označiti z eno od oznak: type:fix, type:features, type:documentation, type:tests, type:config.

Avto-atributiranje pull-requestov
Ko smo že omenili tako temo kot učinkovito delo s pull-requesti, je vredno omeniti še eno dejanje, kot je labeler, ki doda oznake v PR na podlagi tega, kateri datoteki so bili spremenjeni. Na primer, lahko označimo kot [build] vsak pull-request, v katerem so spremembe v imeniku .github/workflow.
Njegovo povezovanje je precej preprosto:
ime: "Samodejno dodeljevanje tem PR"
na:
- pull_request
naloge:
triage:
deluje na: ubuntu-18.04
koraki:
- uporablja: actions/labeler@v2
z:
repo-token: ${{ secrets.GITHUB_TOKEN }}
Potrebovali bomo tudi datoteko s opisom ustreznosti imenikov projekta s temami pull-requestov:
tema:build:
- ".github/**"
- "pom.xml"
- ".travis.yml"
- ".gitignore"
- "Dockerfile"
tema:code:
- "src/main/*"
tema:tests:
- "src/test/*"
tema:documentation:
- "docs/**"
tema:TRASH:
- ".idea/**"
- "target/**"
Nisem uspel povezati dejanja, ki samodejno dodajajo oznake v pull-requeste, in dejanja, ki preverja prisotnost obveznih oznak; match-label očitno ne želi videti oznak, ki jih je dodal bot. Zdi se, da je preprosteje napisati svoje dejanje, ki združuje oba koraka. A tudi v takšni obliki je uporaba precej priročna, ko ustvarjate pull-request, izberete oznako s seznama.
Čas je za uvajanje

Preizkusil sem več možnosti za uvajanje preko GitHub Actions (prek ssh, prek scp in s pomočjo docker-hub) in lahko rečem, da boste verjetno našli način za nalaganje binarne datoteke na strežnik, ne glede na to, kako izviren je vaš pipeline.
Nameloval sem vse infrastrukturo na enem mestu, zato si poglejmo, kako izvesti uvajanje v GitHub Packages (to je repozitorij za binarni vsebino, npm, jar, docker).
Skript za gradnjo docker slike in njeno objavo v GitHub Packages:
name: Deploy docker image
on:
push:
branches:
- 'master'
jobs:
build_docker_image:
runs-on: ubuntu-18.04
steps:
# Build JAR:
- 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 compile package -DskipTests
# Set global environment variables:
- name: set global env
id: global_env
run: |
echo "::set-output name=IMAGE_NAME::${GITHUB_REPOSITORY#*/}"
echo "::set-output name=DOCKERHUB_IMAGE_NAME::docker.pkg.github.com/${GITHUB_REPOSITORY}/${GITHUB_REPOSITORY#*/}"
# Build Docker image:
- name: Build and tag image
run: |
docker build -t "${{ steps.global_env.outputs.DOCKERHUB_IMAGE_NAME }}:latest" -t "${{ steps.global_env.outputs.DOCKERHUB_IMAGE_NAME }}:${GITHUB_SHA::8}" .
- name: Docker login
run: docker login docker.pkg.github.com -u $GITHUB_ACTOR -p ${{secrets.GITHUB_TOKEN}}
# Publish image to github package repository:
- name: Publish image
env:
IMAGE_NAME: $GITHUB_REPOSITORY
run: docker push "docker.pkg.github.com/$GITHUB_REPOSITORY/${{ steps.global_env.outputs.IMAGE_NAME }}"
Najprej moramo zgraditi JAR datoteko naše aplikacije, nato pa izračunamo pot do GitHub docker registry in ime naše slike. Obstaja nekaj trikov, s katerimi se še nismo srečali:
- konstukcija v obliki: echo «::set-output name=NAME::VALUE» omogoča nastavitev vrednosti spremenljivke v trenutnem koraku, tako da jo lahko nato preberemo v vseh drugih korakih.
- Vrednost spremenljivke, nastavljene v prejšnjem koraku, lahko dobimo preko identifikatorja tega koraka: ${{ steps.global_env.outputs.DOCKERHUB_IMAGE_NAME }}
- V standardni spremenljivki GITHUB_REPOSITORY se shrani ime repozitorija in njegov lastnik («owner/repo-name»). Da iz te vrstice izrežemo vse razen imena repozitorija, uporabimo bash sintakso: ${GITHUB_REPOSITORY#*/}
Nato moramo zgraditi docker sliko:
docker build -t "docker.pkg.github.com/antkorwin/github-actions/github-actions:latest"
Prijaviti se v registry:
docker login docker.pkg.github.com -u $GITHUB_ACTOR -p ${{secrets.GITHUB_TOKEN}}
In objaviti sliko v GitHub Packages Repository:
docker push "docker.pkg.github.com/antkorwin/github-actions/github-actions"
Da navedemo različico slike, uporabimo prvih osem številk SHA-hasha commit-a — GITHUB_SHA ima tudi svoje posebnosti, če boste te gradnje izvajali ne le ob združevanju v master, ampak tudi ob ustvarjanju pull-reqeust-a, se lahko SHA ne ujema z hashem, ki ga vidimo v zgodovini gita, ker dejanje actions/checkout ustvari svoj unikatni hash, da se izogne medsebnim blokadam dejanj v PR.

Če je vse potekalo uspešno, lahko v razdelku packages (https://github.com/antkorwin/github-actions/packages) v repozitoriju vidite novo docker sliko:

Tuk lahko vidite seznam različic docker slike.
Samo še nastavite naš strežnik za delo s tem registryjem in znova zaženite storitev. Kako to narediti preko systemd, bom verjetno opisal drugič.
Nadzor
Poglejmo enostavno različico, kako izvesti health check naše aplikacije s pomočjo GitHub Actions. V naši boot aplikaciji je aktuator, zato API za preverjanje njegovega stanja sploh ni potrebno pisati, za lenive je že vse narejeno. Potrebno je samo poklicati gostitelja: 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"}
Vse, kar potrebujemo, je, da napišemo nalogo za preverjanje strežnika po urniku, in če nam slučajno ne odgovori, bomo poslali obvestilo v telegram.
Najprej poglejmo, kako zagnati workflow po urniku:
on:
schedule:
- cron: '*/5 * * * *'
Vse je enostavno, skoraj ne moreš verjeti, da v GitHubu lahko ustvarite takšne dogodke, ki se popolnoma ne ujemajo z webhooki. Podrobnosti so v dokumentaciji:
Preverjanje stanja strežnika bomo naredili ročno preko 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"
Najprej shranimo v spremenljivko to, kar je strežnik odgovoril na zahtevo, v naslednjem koraku preverimo, ali je status UP in, če ni, izstopimo z napako. Če je potrebno ročno 'spustiti' dejanje, potem exit 1 — ustrezno orožje.
- 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 }}
Poslali bomo v telegram le, če je dejanje spodletelo v prejšnjem koraku. Za pošiljanje sporočila uporabljamo appleboy/telegram-action, o tem, kako dobiti botov token in ID chata, lahko preberete v dokumentaciji:

Ne pozabite v GitHubovih skrivnostih zapisati: URL za strežnik in žetone za telegram bot.
Bonus trak — JIRA za lene
Obljubljeno sem, da se bomo vrnili k JIRI, in smo se. Na dnevnih srečanjih sem večkrat opazil, da so razvijalci naredili funkcijo, združili vejo, a so pozabili premakniti nalogo v JIRO. Seveda, če bi vse to počeli na enem mestu, bi bilo lažje, vendar v resnici pišemo kodo v IDE, združujemo veje v Bitbucketu ali GitHubu, naloge pa premikamo v JIRO; za to moramo odpirati nova okna, včasih se znova prijaviti itd. Ko se dobro spomniš, kaj moraš narediti naprej, pomeni, da ni smiselno še enkrat odpirati nadzorne plošče. Na koncu pa je na dnevnem srečanju potrebno posvetiti čas ažuriranju nalog.
GitHub nam bo pomagal tudi pri tem rutinskem opravilu; na začetku lahko naloge avtomatsko premikamo v stolpec code_review, ko oddamo pull request. Vse, kar potrebujemo, je, da se držimo dogovora o poimenovanju vej:
[ime projekta]-[številka naloge]-naziv
na primer, če bo ključ projekta »GitHub Actions« GA, potem GA-8-jira-bot lahko predstavlja vejo za izvedbo naloge GA-8.
Integracija z JIRO deluje preko akcij Atlassiana, ki niso idealne; moram reči, da nekatere od njih sploh niso delovale zame. Toda razpravljali bomo le o tistih, ki zagotovo delujejo in se aktivno uporabljajo.
Najprej je treba opraviti avtentifikacijo v JIRI z uporabo dejanja: 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 }}
Za to je treba pridobiti žeton v JIRI; kako to storiti, je opisano tukaj:
Iz imena veje izvlečemo identifikator naloge:
- 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 ime veje: $GITHUB_HEAD_REF
echo izluščen problem: ${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 "Prosim, imenujte svojo vejo v skladu z nalogo JIRA: [project_key]-[task_number]-branch_name"
exit 1
fi
echo uspešno najden problem JIRA: ${{steps.find_issue.outputs.ISSUE_ID}}
Če iščete na GitHub marketplace, lahko najdete dejanje za to nalogo, vendar sem moral tudi sam napisati enako s pomočjo grepa po imenu veje, ker to dejanje od Atlassiana ni želelo delovati v mojem projektu; ugotavljati, kaj narobe, je trajalo dlje, kot da bi enako naredil ročno.
Just move the task to the "Code review" column when creating a pull request:
- name: Transition issue
if: ${{ success() }}
uses: atlassian/gajira-transition@master
with:
issue: ${{ steps.find_issue.outputs.ISSUE_ID }}
transition: "Code review"
There is a special action for this on GitHub; all it needs is the issue ID obtained in the previous step and authorization in JIRA, which we did earlier.

Similarly, tasks can be dragged during merges into master and during other events from the GitHub workflow. In general, it all depends on your imagination and desire to automate routine processes.
Zaključki
If we look at the classic DEVOPS diagram, we've covered all stages, except perhaps for operation. I believe we can find some action in the marketplace for integration with a help-desk system, so let's say the pipeline turned out to be solid, and based on its use, we can draw conclusions.

Prednosti:
- The marketplace with ready-made actions for all occasions is really cool. In most of them, you can also view the source code to understand how to solve a similar problem or post a feature request to the author directly in the GitHub repository.
- Choosing a target platform for building: Linux, Mac OS, Windows is quite an interesting feature.
- GitHub Packages is great; keeping all infrastructure in one place is convenient. You don't have to surf through different windows; everything is within one or two clicks of the mouse and perfectly integrated with GitHub Actions. Docker registry support in the free version is also a nice advantage.
- GitHub hides secrets in build logs, so using it to store passwords and tokens isn't so scary. Throughout all my experiments, I never managed to see a secret in plain text in the console.
- Free for open-source projects
Mane:
- YML, I really don't like it. When working with such a flow, my most frequent commit message is "fix yml format"; sometimes you forget to place a tab somewhere or write on the wrong line. In general, sitting in front of the screen with a protractor and ruler is not the most pleasant task.
- DEBUG, debugging the flow with commits, starting a rebuild, and outputting to the console is not always convenient, but it's more of a case of "you're spoiled," accustomed to working with convenient IDEs where you can debug anything.
- Svoj akcijski kod može se napisati na bilo kojem jeziku ako se obavije u Docker, ali nativno podržava samo JavaScript. Naravno, to je stvar ukusa, ali ja bih radije nešto drugo umjesto JS-a.
Podsećam vas da je repozitorij sa svim skriptama ovde:
Sledeće nedelje ću se pojaviti na konferenciji Heisenbug 2020 Piter. Ispričaću ne samo kako izbeći greške prilikom pripreme testnih podataka, već ću podeliti i svoje tajne rada s skupovima podataka u Java aplikacijama!
Izvor: habr.com
