Jak dodać ten sam parametr do trzydziestu jobów? Groovy DSL w służbie Jenkinsa

Jenkins jest jednym z najpopularniejszych narzędzi służących do automatyzacji procesu dostarczania oprogramowania. Mimo wciąż rosnącej konkurencji, wiele firm wybiera go jako jedno z pierwszych rozwiązań, które wdrażają w celu usprawnienia pracy firmy. Jest dość intuicyjny, prosty w obsłudze (zwłaszcza na samym początku) i pozwala na integrację z wieloma innymi narzędziami. 

Emil Wypych. DevOps Engineer w Genesis Global Limited. DevOps z krwi i kości, fan Elsy, krwiożerczych roślinek i niedźwiedzi. Gdy trzeba potrafi rzucić wyjątkiem tam gdzie nie powinien, by pomóc w diagnozie niereprodukowalnych problemów. Innymi słowy białkowa wersja debuggera. Doskonały słuchacz, negocjator i pisarz w jednym, a oprócz tego łącznik pomiędzy światem dev a światem ops.


Podstawową jednostką konfiguracyjną jest job, który może zostać w prosty sposób „wyklikany” w graficznym interfejsie. Jest to najprostszy i najłatwiejszy sposób, który jednak zaczyna być kłopotliwy wraz ze wzrostem liczby skonfigurowanych jobów. Co bowiem zrobić, kiedy musimy dodać taki sam parametr do trzydziestu różnych jobów? Robić to ręcznie? Nie brzmi to zbyt zachęcająco. Na całe szczęście mamy możliwość skorzystania z Groovy’ego oraz pluginu Jenkins DSL.

Czym dokładnie jest nasz problem?

Wyobraźmy sobie taką sytuację: mamy serwer Jenkinsa odpowiedzialny za niemalże całą automatyzację w firmie. Począwszy od uruchamiania testów, poprzez budowanie paczek, wrzucanie ich na serwery, a zakończywszy na cyklicznych zadaniach wykonywanych dla developerów, czy czegokolwiek tylko nie wymyślimy. Kilkadziesiąt do kilkuset skonfigurowanych jobów to coś normalnego, zwłaszcza w większych organizacjach. 

Wszystko działa pięknie dopóki nie musimy czegoś edytować w dowolnym jobie. W jednym to jeszcze pół biedy – wyklikamy to bez najmniejszego problemu. Co jednak, jeśli musimy dorzucić dodatkowy parametr do, powiedzmy, kilkunastu jobów, które mają bardzo podobną konfigurację? Boli, prawda? Ojjj, jak boli.

Gdyby tylko ktoś wymyślił…

Gdyby tylko ktoś wymyślił coś, co pozwoli na przykład wrzucić konfigurację w jakąś pętlę i utworzyć joby iterując po nich… Wtedy można by było dokonać zmiany w jednym miejscu! Ewentualnie wykorzystać narzędzie replace dowolnego edytora tekstowego/IDE i coś zmienić. Albo dorzucić sedem. I nie, nie bezpośrednio w XMLach Jenkinsa, bo przydałoby się wersjonowanie zmian. Możemy przeglądać historię konfiguracji jobów dzięki odpowiedniemu pluginowi, ale hej, jesteśmy przecież nowoczesną organizacją – chcemy wszystko wsadzić do Gita! Względnie innego systemu kontroli wersji. Ach te marzenia… Na całe szczęście już dawno zostały spełnione, wystarczy więc, że po nie sięgniemy.

DSL? Cyfrowa linia abonencka w służbie Jenkinsa?

Pewnie wiele osób słysząc lub widząc skrót DSL pomyśli sobie, że chodzi o szerokopasmowy dostęp do internetu (he-he, to ten moment, w którym sięgacie po butelkę wody, dbam o Wasze nawodnienie). W przypadku Jenkinsa chodzi jednak o domain-specific language, czyli język dziedzinowy. Jest to w dużym skrócie język dostosowany do rozwiązania konkretnego problemu lub całej dziedziny (np. właśnie konfiguracja narzędzia, tworzenie zawartości szczególnego portalu), a nie do ogólnego użytku. Na przykład za pomocą języka HCL, który jest charakterystyczny dla produktów Hashicorp (być może kojarzycie takie narzędzia jak Terraform czy Vault), który nie służy wszystkim narzędziom w ten sam sposób. Konfiguracja napisana dla Terraforma nie zadziała dla Vaulta. 

Język DSL może jednak być (co widać też w przykładzie z Terraformem i Vaultem) oparty na jakimś konkretnym języku programowania lub skryptowym, a jednocześnie być wykorzystywanym w wielu przypadkach. XML jest jednym z najprostszych przykładów – może być wykorzystywany np. w Waszych aplikacjach do definiowania konfiguracji. Ba, YAML, którego zapewne wszyscy bardzo kochają, pojawia się w większości współczesnych narzędzi wykorzystywanych przy jakiejkolwiek automatyzacji.

Groovy również często jest wykorzystywany właśnie jako baza dla DSL, głównie dzięki swojemu „uproszczeniu” w stosunku do Javy (tak, wiem, teraz się te granice w pewnym sensie zacierają i na pewno pojawią się głosy, że rozwijanie Groovy’ego przy obecnej formie Javy nie ma sensu). 

Zważywszy na fakt, że cały Jenkins napisany jest w Javie, a Groovy’ego wykorzystuje między innymi w wielu pluginach jako element konfiguracji, nic dziwnego, że został również wykorzystany przez twórców pluginu Jenkins DSL Plugin. To właśnie on pozwala nam na wykorzystanie tego języka do łatwego i szybkiego (he-he) zamienienia naszych wyklikiwalnych jobów w kod. Który możemy wsadzić w gita! Git, no nie?

Nasz pierwszy hello world!

Nie no, bez przesady, nie będziemy robić żadnego hello world. W sumie „nie da się”, jak głosi jedna z najbardziej popularnych odpowiedzi programisty na pytanie, które zadał Product Owner. W końcu chodzi nam nie o wylistowanie czegokolwiek na ekranie, a stworzenie pełnoprawnego joba. Sama budowa jest trywialna i ogranicza się do tego samego, co robimy podczas wyklikiwania freestyle jobów (bo na nich będziemy opierać niniejszy krótki poradnik, co nie znaczy, że tylko do nich ogranicza się Jenkins DSL), czyli zestawienia ze sobą kilku klocków, ale w postaci kodu. 

All you need is love!

A dokładniej miłość do Groovy’ego oraz Jenkins DSL Plugin, który jest cały czas aktywnie rozwijany. Zainstalować go można poprzez Plugin managera, a po jego instalacji będziemy mogli wybrać nowy Build step podczas konfiguracji tradycyjnego freestyle joba – „Process Job DSLs”. To właśnie on nas interesuje.

Jako pierwszy krok należy więc stworzyć tak zwany seed job, który będzie w stanie uruchomić nasz kod napisany w Groovym i utworzyć skonfigurowane przez nas joby. Jak więc się już pewnie domyślacie, na początek należy stworzyć nowy freestyle job i jako Build step wybrać „Process Job DSLs”. Tutaj będziemy mieli do wyboru tak naprawdę tylko dwie opcje:

  • Use the provided DSL script
  • Look on Filesystem

Pierwszy z nich pozwoli nam wrzucić cały skrypt bezpośrednio do Build stepu i trzymać go w konfiguracji Jenkinsa. Jest to łatwiejsze i szybsze do testowania, ponieważ kod możemy po prostu wkleić. Docelowo, w produkcyjnym użyciu o wiele lepsza jest jednak ta druga opcja, ponieważ zakłada ona wstępne zrzucenie pliku z kodem do filesystemu Jenkinsa. W jaki sposób zrzucenie, zapewne zapytacie? Ano w najprostszy z możliwych – zaciągając go z repozytorium Gita na ten przykład! A w końcu lubimy kontrolę wersji, prawda? Tak, tak, skrypty zawierające konfigurację jobów jenkinsowych również powinny być trzymane w systemie kontroli wersji. Jak każdy kod.

Na potrzeby niniejszego artykułu wystarczy nam jednak w zupełności „Use the provided DSL script”.

No dobra, to gdzie ten Groovy?

Już się produkuje! Zanim jednak przejdziemy do nieco bardziej szczegółowego opisu, spójrzcie prosze na ten oto fragment:

No dobra, żartowałem, że nie będzie Hello world. Jednak jest. Powyższy kod utworzy joba o nazwie “Hello world”, który będzie miał tylko jeden Build step – „Execute shell”, w którym wykonamy zwykłe echo. Jeśli lubicie XMLe jenkinsowe, to efektem wykonania powyższego skryptu Groovy będzie coś takiego:

Tak, to jest jeden z najprostszych przykładów, który jedyne na co nam pozwoli, to odpalenie w shellu zwykłego echo. Jeśli zajrzymy do klocków, z których zbudowany jest taki job, to tak naprawdę ujrzymy same domyślne ustawienia oraz jeden, jedyny krok, który się wykona.

A może tak chociaż dodać SCM?

SCM? Tak, Source Control Management. Jenkins umożliwia wykorzystanie niemalże każdego istniejącego narzędzia SCM – zarówno na poziomie rozróżniania Gita czy SVNa, jak również wyboru wykorzystywanego w danym projekcie serwera Git (Bitbucket, GitLab, GitHub, własne rozwiązanie). Praktycznie żaden proces CI/CD nie może istnieć bez kontroli wersji. Przyjmijmy na potrzeby niniejszego artykułu, że używany Gita, a dokładniej Bitbucketa jako narzędzia do obsługi zdalnego repozytorium. Konfiguracja będzie analogiczna dla dowolnego innego narzędzia lub systemu kontroli wersji. Wystarczy jedynie wgryźć się w dokumentację i dokopać się do interesujących nas metod.

W takim razie dodajmy do tego naszego Hello World obsługę gita. Żeby trochę utrudnić sobie sprawę (lub ułatwić, w zależności jak na to spojrzymy), zamieńmy proste echo na odpalenie skryptu pythonowego, który będzie wypisywał to Hello World za nas. Oczywiście przyjmujemy, że skrypt ten istnieje już w repozytorium i działa! Oto kroki, które musimy wykonać w jobie:

  1. Sklonowanie repozytorium znajdującego się pod adresem https://bitbucket.org/example/hello-world
  2. Uruchomienie w shellu skryptu pythonowego o nazwie hello_world.py

Nasz skrypt DSL, który ma utworzyć takiego joba wyglądać więc będzie w ten sposób:

Pewnie zastanawia co niektórych z Was, czym jest to magiczne credentials. W końcu url jest dość oczywisty, branch również (wskazuje który branch powinien zostać sklonowany). Credentials to nic innego jak wskazanie klucza prywatnego, którym Jenkins spróbuje uwierzytelnić się do Bitbucketa, jeśli jest to repozytorium prywatne. Słowo credential nie zostało wykorzystane przypadkowo – w końcu takich rzeczy jak prywatne klucze SSH nie chcemy wrzucać gdziekolwiek plaintextem. Dlatego też podajemy tutaj jedynie ID poświadczeń, które wcześniej skonfigurowaliśmy, a które przechowywane i wykorzystywane w jobach są w sposób bezpieczny. 

Jeśli nie wiecie jak skonfigurować credentiale, możecie zerknąć na krótki post, który kiedyś popełniłem (uwaga, w łamanym angielskim, osoby z biegłością językową upraszane są o łyknięcie melisy przed lekturą), a który opisuje właśnie cały proces, jak i ideę czegoś, co się w Jenkinsie zwie credentials. 

W tym przypadku nasz wygenerowany XML z konfiguracją joba będzie wyglądał w następujący sposób:

Prawda, że proste? W ten sposób można dorzucać kolejne klocki i kawałki konfiguracji jobów, począwszy od takich rzeczy jak ustawienie repozytorium, z którego ma być pobierany kod źródłowy, aż po kolorowanie wyników w konsoli czy ustawianie retencji zebranych lokalnie artefaktów.

To teraz wiele podobnych Hello Worldów!

Tak naprawdę Jenkins DSL na niewiele by się nam zdał, gdyby pozwalał tworzyć jedynie pojedyncze joby. W końcu mimo wszystko łatwiej jest modyfikować je w GUI (zazwyczaj). Oczywiście można by tworzyć takie same joby z lekkimi modyfikacjami wykorzystując starą, sprawdzoną metodę Copy’ego-Pasty, ale przecież nie o to chodzi, prawda? Skoro wykorzystujemy tutaj Groovy jako język podstawowy, to czemu byśmy nie mogli skorzystać z takiej wspaniałości jak pętla? Ano moglibyśmy. I to zrobimy.

Podzielimy się na środowiska

Jenkins sam w sobie nie posiada czegoś takiego jak logiczny podział na środowiska, więc nie możemy stworzyć jobów, które odpowiedzialne by były za obsługę konkretnych środowisk. Oczywiście znowu muszę bardzo mocno uprościć kolejną kwestię na potrzeby niniejszego artykułu i założyć, że chcemy skonfigurować osobne joby dla trzech środowisk – dev, stage oraz prod – na które planujemy wrzucić zawartość wspomnianego wcześniej repozytorium i uruchomić ten sam skrypt. 

Rzecz jasna w prawdziwym życiu raczej nie będziemy wykorzystywać tego w ten sposób, chociaż w zależności od języka aplikacji oraz sposobu jej releasowania być może akurat ten przykład będzie pomocny. W każdym razie na pewno pokaże on w jaki sposób można wykorzystać pętle w Jenkins DSL do naszych celów, niezależnie od tego, jakie by nie były.

W takim razie założenia dla tego przykładu są następujące:

  1. Docelowo mają powstać trzy joby.
  2. Każdy z nich odpowiedzialny jest za osobne środowisko.
  3. Każdy z nich uruchamia ten sam skrypt (jakkolwiek irracjonalne by to nie było).

Tworzymy w takim razie kod, który wygląda mniej więcej tak (opiera się na kodzie z poprzedniego przykładu):

W wyniku otrzymamy taki oto kod XML:

Jak widzicie, utworzył on trzy różne joby, które tak naprawdę w niniejszym przykładzie robią dokładnie to samo, jednak sama zasada została zachowana. Oczywiście można tutaj dodawać słowniki i odwołania do konkretnych elementów, instrukcje warunkowe oraz całą resztę dobrodziejstwa, jakie może nam zapewnić Groovy. Ogranicza nas jedynie nasza wyobraźnia tak naprawdę – sky is the limit, jak to mawiają w ojczyźnie Shakespeare’a.

Skąd wziąć metody i jak je przećwiczyć?

Rozpisałem się pokazując jakieś tam losowe przykłady, ale nie pozwolą one Wam sprawnie posługiwać się tymi wspaniałościami, prawda? Przydałaby się chociaż dokumentacja, a najlepiej jeszcze jakiś playground do ćwiczeń. Oczywiście wszystko zostało już przygotowane!

Jenkins DSL API reference

Po zainstalowaniu pluginu Jenkins DSL dostajemy dostęp do dokumentacji, do której możemy zajrzeć bezpośrednio z poziomu naszej instalacji Jenkinsa. Oczywiście można się do niej dobrać również gdzieś w czeluściach internetu, ale nie polecam. Dlaczego? Dlatego, że ta dokumentacja dostępna z poziomu własnego Jenkinsa uwzględnia wszystkie pluginy, które są zainstalowane na danej instancji. Jest to o tyle ważne, że Jenkins DSL co prawda nie daje pełnego wsparcia na wszystkie możliwe pluginy, jakie zostały napisane, ale jeśli tylko twórcy danego pluginu zadbali o implementację integracji z Jenkins DSL, to plugin jest w stanie wygenerować dynamicznie konfigurację i podać odpowiednie metody. Oczywiście możliwe jest to tylko w przypadku przeglądania dokumentacji z poziomu własnej instancji Jenkinsa.

Można się do rzeczonej dokumentacji dostać na jeden z dwóch sposobów: 

  1. https://YOUR_JENKINS_URL/plugin/job-dsl/api-viewer/index.html
  2. Wejść w widok dowolnego, skonfigurowanego SEED JOBa i wybrać z lewego menu „Job DSL API Reference”.

W niej znajdziemy z lewej strony podział na kontekstu (parametry, konkretne stepy, listy, widoki etc.), a na górze wyszukiwarkę, dzięki której możemy szybko odnaleźć interesujący nas zestaw dla konkretnego pluginu.

Testujemy, ale nie na produkcji!

Teraz pewnie wiele osób opuściło smutno głowy, kiedy zobaczyły, że piątkowe deploye SEED jobów właśnie uciekają gdzieś w stronę zachodzącego słońca. Oczywiście jeśli jesteście twardzi to możecie testować Wasze skrypty bezpośrednio na Jenkinsie, jednak wcale nie trzeba tego robić. Po pierwsze, można zaimplementować testy jednostkowe (tak! serio!) i sprawdzać poprawne wykonanie w zautomatyzowany sposób, a po drugie, można skorzystać z Jenkins DSL Playground, który dostępny jest w kilku różnych wariantach.

Jeśli chodzi o same testy jednostkowe, to jest to temat na osobny artykuł, jednak zainteresowanych odsyłam do dokumentacji na stronie pluginu, która zawiera linki do przykładowych zastosowań.

Nad drugim tematem pochylę się jednak chwilę, bo zdecydowanie warto. Istnieje sobie takie magiczne repozytorium tworzone przez Matta Sheehana, które zawiera kod źródłowy playgrounda. Można w nim zasymulować wykonywanie skryptów DSLowych, które przekształcane są na XML, na którym pracuje Jenkins. Aby skorzystać z niego i odpalić sobie go lokalnie na własnej stacji, wystarczy sklonować repozytorium oraz uruchomić za pomocą komendy ./gradlew run. Rzecz jasna wymaga jest Java! 

Jeśli ktoś nie ma/nie chce/nie ma potrzeby/nie życzy sobie/nie skala się instalacją Javy na własnej maszynie, to może skorzystać z jednej z dwóch opcji:

  1. Użyć wystawionej aplikacji webowej przez autora tego narzędzia.
  2. Użyć obrazu dockerowego stworzonego przeze mnie i uruchomić własną lokalnie.

Pierwsza opcja jest szybka – po prostu wchodzicie na stronę i po lewej stronie wklejacie Wasz DSL, a po prawej otrzymujecie wynikowy XML (lub informację, że coś srogo zepsuliście). 

Jeśli się jednak boicie wrzucać jakichś wrażliwych danych gdzieś do internetu, to możecie skorzystać z obrazu dockerowego pobłogosławionego przez autora tego projektu i postawić sobie taki playground lokalnie. W tym celu zajrzyjcie na hub.docker.com, gdzie znajdziecie krótką instrukcję w jaki sposób uruchomić kontener i jak go używać. Jeśli chciecie nieco więcej informacji na ten temat, to mogę zaprosić ponownie do krótkiego posta mojego autorstwa, który pochyla się nad tym narzędziem – tylko ponownie wymagana cierpliwość do mojego angielskiego. 

Podsumowanie

Zastanawiałem się dość długo czy oraz w jakiej formie napisać ten artykuł. Nie jestem programistą, nie wywodzę się z tego szlachetnego zawodu, dlatego wydawało mi się nieco dziwne pisanie o Groovym i jego wykorzystaniu do codziennej pracy z Jenkinsem, zwłaszcza, że każdy szanujący się programista Javy padłby na zawał widząc mój kod. Zdaję sobie sprawę z tego, że wiele osób napisało by go o wiele lepiej ode mnie, jednak ciężko mi przejść obojętnie obok pewnego rodzaju nieświadomości istnienia tego narzędzia. 

Widziałem w wielu miejscach – na Medium, Reddicie, Hacker News – że ludzie pracujący codziennie z Jenkinsem, z wieloma naprawdę jobami (nie tylko w postaci pipeline’ów zasysanych z repozytorium) nie mają bladego pojęcia o istnieniu Jenkins DSL. Jeśli ktoś już znał, to podnosił argument, że nie da się go w ogóle przetestować. Jakoby jedynym sposobem jest testowanie na produkcji. Kiedy zabrałem się za konspekt tego artykułu, to nie wiedziałem od czego zacząć. W końcu jak widać tematów jest mnóstwo! Sam odkrywałem je po kolei, kopiąc coraz głębiej w czeluściach internetu i dokumentacji. Pomimo tego, że wyszło całkiem długo, to wiem, że nie poruszyłem jeszcze mnóstwa rzeczy, które mogą się przydać.

Nie wspomniałem o wykorzystaniu klas i pisaniu własnych metod do unikania powielania kodu – a warto przecież od razu zacząć tak pisać kod, aby je uwzględniać. Niestety temat ten to też temat rzeka (na swoim blogu opisałem w jaki sposób można stworzyć stałe do wykorzystania w jobach – tak, to jest tylko prosty przykład stworzenia stałych; drugim przykładem może być ustawianie uprawnień dla jobów z wykorzystaniem Jenkins DSL oraz Authorization Matrixjak widać kolejna kobyła, poruszająca jedynie malutki wyrywek problemu). 

Niechaj więc pewnego rodzaju podsumowaniem i konkluzją niniejszego artykułu będzie zachęcenie Was do kopania i to jak najgłębszego. Jeśli we współczesnym IT macie jakiś problem, to prawdopodobnie nie jesteście jedyni. Ktoś już taki problem miał i jest spore prawdopodobieństwo, że za Was go już rozwiązał. Tak jak to jest zarówno z samym utrzymywaniem konfiguracji jobów Jenkinsa jako kodu, jak również z takimi rzeczami jak testy tego kodu (swoją drogą wiecie, że można również pisać testy jednostkowe do pipeline’ów w Jenkinsie?).


Zdjęcie główne artykułu pochodzi z unsplash.com.

Zapraszamy do dyskusji

Patronujemy

 
 
Polecamy
Jak zaoszczędzić na AWS? Poznaj LocalStack