#python #azure
Ostatnio miałem ciekawy problem ci/cd do rozwiązania :P A mianowicie testowanie aplikacji mikroserwisowej w ekosystemie Azure'a przy użyciu cli do lokalnego postawienia funkcji.
Problem był natury wątkowej. Oprócz postawienia serwera funkcji, potrzebowałem również postawić serwer drugiej apki (agregatora danych, fastApi) z którego całość korzysta.
W przypadku azure devops, natywny paraleism działa na poziomie jobów i stage. Wszystkie te byty mogą działać osobno a wymiana danych pomiędzy nimi może polegać chociażby za pomocą artefaktów. Jednak tworzy to kolejny problem a mianowicie budowanie artefaktów, które mają swoje limity, ale nie o tym.
W tym case potrzebowałem trzech osobnych procesów. Jeden odpowiedzialny za postawienie lokalnego cli azure functions. Drugi za odpalenie lokalnego serwer fastApi. Trzeci do egzekucji pytesta.
Do sprawnej komunikacji, wszystko powinno odbyć się w obrębie jednego "taska" po stronie azure devops. Dzięki temu nie trzeba cudować z dodatkową konfiguracją w przypadku np jobów, tak, żeby azure function widziało lokalną wersję fastApi i odwrotnie.
Niestety azure nie posiada "oficjalnego" podejścia w przypadku paraleismu na poziomie pojedyńczego (synchronicznego) taska.
Z pomocą przychodzi import subprocess
:) Biblioteka standardowa pozwalająca egzekwować komendy bashowe "pod spodem". Rozwiązaniem okazała się klasa Popen
, która jest niczym innym jak nowym procesem. Dodatkowo jest o tyle fajna, że jej wywołanie zwraca adres procesu, którym możemy sterować z wątku nadrzędnego. Możemy go ubić, zatrzymać, czy przerzucić output.
Finalnie rozwiązaniem okazał się prosty moduł testowy (psudo kod):
fastapi = subprocess.Popen(...)
az_functions = subprocess.Popen(...)
subprocess.run("pytest odpal testy suuuko ;p") # funkcja synchroniczna, która stopuje wątek główny.
fastapi.terminate()
az_functions.terminate()
Następnie po stronie taska w pipeline azure devops, wywoałnie banalnie proste:
- task: AzureCLI@2
inputs:
azureSubscription: sub
scriptType: 'bash'
inlineScript: |
python3.10 nasz_modul.py