Podstawową zasadą, którą wpaja się wszystkim młodym programistom, jest reguła reużywania kodu. Wiadomo - zamiast robić przeklęte ctrl+C i ctrl+V, lepiej jest zbudować reużywaną funkcję/moduł, dzięki czemu później zmianę wystarczy zrobić w jednym miejscu. Tę regułę można też zastosować do pipeline’ów YAML-owych w AzureDevops. W tym odcinku devopsowych tips & tricks, przyjrzymy się, jakie są opcje.
Template nesting
Pierwsza opcja to “zagnieżdżanie” szablonów, czyli “referencjonowanie” za pomocą template:
steps:
- script: echo "Hello world"
- template: ./nested-template.ymlJest to podstawowa opcja, oczywiście możemy przekazać do takiego zagnieżdżonego szablonu parametry. Co ciekawe, szablon może znajdować się w innym repozytorium - wówczas musimy zdefiniować owo repozytorium jako zasób w sekcji resources i potem po nazwie zasobu odwołać się do szablonu, np. nested-template.yml@yaml-repo. Takie rozwiązanie nie wydaje się naturalne (raczej chcemy trzymać YAMLe pipeline’ów obok aplikacji), ale przy większych projektach i/lub organizacjach możemy mieć np. bazowe szablony dla wielu projektów/zespołów w oddzielnym repozytorium, którym opiekuje się dedykowany zespół.
Oficjlanie istnieją pewne ograniczenia ilości zagnieżdżeń oraz łącznego rozmiaru wszystkich YAMLów biorących udział w buildzie (zainteresowanych odsyłam do dokumentacji)- ale mnie się nie udało zderzyć ze ścianą. (Próbowałem dojść do limitu zagnieżdżeń.)
Pętle
Kolejną konstrukcją, którą adept programowania poznaje na wczesnym etapie, a którą też da się zastosować w pipeline’ach są pętle. Tutaj nie ma specjalnej filozofii: możemy iterować po listach. (Aczkolwiek de facto nie ma typu danych “lista” w Azure DevOps, no ale typy danych to może temat na oddzielny odcinek.) Przykładowo:
parameters:
- name: characters
type: object
default:
- Tytus de Zoo
- Romek
- A'Tomek
- steps:
- ${{ each character in parameters.characters }}:
- script: echo "Hi, ${{ character }}"Przy pisaniu trzeba tylko uważać na wcięcia i ważny dwukropek na końcu linijki, w której stosujemy słówko kluczowe each.
Matrix strategy
Bardziej wymyślną pętlą, zastosowaną na całym jobie i bardziej explicite wyrażoną jest owo matrix strategy. Zasada jest tu taka, że na poziomie joba określamy scenariusze jego uruchomienia, tzn. dla każdego scenariusza definiujemy wartość zmiennych, uzyskująć w ten sposób efekt podmiany ich wartości dla każdego scenariusza. Spójrzmy:
jobs:
- job: MatrixSample
strategy:
matrix:
v1:
url: https://somwhere.com/old/api
v2:
url: https://somwhere.com/new/api
steps:
- bash: curl ${{ url }}W powyższym przykładzie job wykona się dwa razy, dla scenarisuzy v1 i v2, które wykonają request dla starej i nowej wersji api. Oczywiście tutaj przykład wydumany, API powinno być wersjonowane explicite w URL czy w nagłówku, ale tak chyba lepiej widać, o co chodzi. Sam Microsoft w dokumentacji podaje przykład, że matrix strategy może być używane np. do uruchamiania testów na różnych platformach. O tym będzie, ale w innym odcinku. :)
Extend template
Ostatnia opcja to roszerzanie szablonów za pomocą słówka kluczowego extend. Jest do de facto dziedziczenie po innym szablonie. Może być przydatne w scenariuszu opisanym wyżej, czyli: bazowe szablony, wspólne dla wielu projektów, umieszczamy w innym repozytorium, a lokalnie dziedziczymy po nich i konstruujemy własne pipeline’y.
Jak działa to dziedziczenie? W podstawowej opcji, jest to po prostu odwołanie się do szablonu bazowego i przekazanie parametrów. Czyli prawie to samo, co zagnieżdżanie, tyle że robione na poziomie całego szablonu.
# base.yml
parameters:
- name: yourName
type: string
steps:
- script: echo "Hello, ${{ parameters.yourName }}, I am base template".# extending.yaml
extend: base.yml
parameters:
- yourName: RobertSam Microsoft w dokumentacji sugeruje, że ten mechanizm można wykorzystać jak prawdziwe “dziedziczenie”: skoro typem parametru może być lista stepów, to dlaczego nie zaprojektować bazowego szablonu tak, aby przyjmował taki opcjonalny parametr. W ten sposób możemy zaimplementować odpowiednik funkcji wirtualnej lub abstrakcyjnej - konstruktów z programowania obiektowego.
# base.yml
parameters:
- name: yourName
type: string
- name: preSteps
type: stepList
default: [] # thanks to default value this is 'virtual'
- name: postSteps
type: stepList
steps:
- ${{ each step in parameters.preSteps}}:
- ${{ step }}
- script: echo "Hello, ${{ parameters.yourName }}, I am base template".
- ${{ each step in parameters.postSteps}}:
- ${{ step }}# extending.yaml
extend: base.yml
parameters:
- yourName: Robert
- postSteps:
- script: echo "Goodbye!"Podsumowanie
Jak widać, przy głębszym poznaniu, AzureDevops oferują całkiem niezłe konstrukty do budowania zaawansowanych szablonów. Co więcej, dokumentacja jest całkiem nieźle napisana i zawiera przykłady. Z własnej praktyki mogę przyznać, że dotychczas “produkcyjnie” użyłem wszystkich wyżej wymienionych opcji, prócz matrix strategy - czyli nie są to tylko zabawki, ale realne pomoce, które warto mieć w swojej skrzynce narzędziowej.