Wprowadzenie
Możemy spotkać różne sposoby organizacji projektów w repozytoriach. Niektórzy preferują podejście monorepo, w którym wiele różnych projektów/aplikacji znajduje się w tym samym wspólnym repozytorium. Innym podejściem jest multirepo, gdzie dla każdego projektu/aplikacji tworzymy dedykowane repozytorium.
Jednym z problemów pierwszego podejścia jest optymalizacja budowania aplikacji w procesie CI/CD, bo nie chcemy budować wszystkich projektów w repozytorium, w momencie gdy zmienił się tylko jeden. W Azure DevOps możemy rozwiązać ten problem poprzez zastosowanie path filters w definicji pipeline.
Monorepo vs Multirepo
W tym wpisie nie będę chciał zagłębiać się w porównanie tych dwóch sposobów organizacji projektów/repozytoriów. Szczególnie że można spotkać różne warianty obu podejść (np. monorepo per zespół). Jeśli interesuje Cię ten temat, to zachęcam do przesłuchania 27. odcinka podcastu Patoarchitekci, w którym Łukasz Kałużny oraz Szymon Warda poruszają ten temat – https://patoarchitekci.io/27/.
Swoją drogą Łukasz i Szymon robią świetną robotę. Jeśli nie znasz ich podcastu, to gorąco zachęcam do słuchania. Bardzo dużo super wiedzy, podanej w krótki i skondensowany sposób.
Buildy w Monorepo
W podejściu monorepo na ogół na głównym poziomie repozytorium dla każdej aplikacji/projektu mamy dedykowany folder, w którym następnie znajduje się cała zawartość projektu (kod, testy, dokumentacja).
Na potrzeby tego wpisu przygotowałem proste repozytorium, w którym są dwa projekty – project1 oraz project2:
To co chcielibyśmy osiągnąć, to efektywne budowanie obu projektów. Czyli jeśli zmiany pojawiły się tylko w folderze project1, to chcemy uruchomić pipeline tylko dla tego projektu. Szkoda tracić czas i zasoby na budowę drugiego projektu, gdy w nim nie pojawiła się żadna zmiana. W Azure DevOps możemy coś takiego osiągnąć z wykorzystaniem path filters.
Path filters w Azure DevOps
W przykładzie każdy z projektów w swoim folderze będzie miał plik azure-pipelines.yml, w którym znajduje się definicja pipeline. Przykładowe projekty nie są mocno rozbudowane. Są to wygenerowane projekty WebApi w .NET 5, w których praktycznie nic nie zmieniałem.
Plik azure-pipelines.yml pierwszego projektu wygląda tak:
# ASP.NET Core (.NET Framework) | |
# Build and test ASP.NET Core projects targeting the full .NET Framework. | |
# Add steps that publish symbols, save build artifacts, and more: | |
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core | |
trigger: | |
branches: | |
include: | |
- master | |
paths: | |
include: | |
- project1/* | |
exclude: | |
- project1/azure-pipelines.yml | |
pool: | |
vmImage: 'windows-latest' | |
variables: | |
solution: 'project1/*.sln' | |
buildPlatform: 'Any CPU' | |
buildConfiguration: 'Release' | |
steps: | |
- task: NuGetToolInstaller@1 | |
- task: NuGetCommand@2 | |
inputs: | |
restoreSolution: '$(solution)' | |
- task: VSBuild@1 | |
inputs: | |
solution: '$(solution)' | |
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' | |
platform: '$(buildPlatform)' | |
configuration: '$(buildConfiguration)' |
Z naszego punktu widzenia kluczowy jest początkowy fragment pliku (linijki 6-14). Znajduje się w nich informacja o sposobie wyzwalania danego pipeline. Tutaj jest ustawiony trigger dla branch master (czyli zmiany w tym branchu spowodują uruchomienie tego pipeline). To jest standardowy elementy tego typu plików, który zapewne widziałeś/widziałaś nie raz.
Kolejny element pliku (od linijki 10) to właśnie nasz tytułowy path filters. W nim określamy wzorzec ścieżki, który zostanie użyty do filtrowania zmiany. W tym przypadku mówimy, że interesują nasz wszystkie zmiany w folderze project1 (opcja include), poza zmianą w pliku azure-pipelines.yml (opcja exclude). Tutaj możemy podać po kilka różnych wzorców dla każdej z opcji (na przykład wykluczyć zmiany w folderze z dokumentacją).
Warto jeszcze zwrócić uwagę na jedną zmianę, jaką zrobiłem w stosunku do tego, co domyślnie generuje Azure DevOps. W linijce 20 znajduje się zmienna dla pliku solution, który później jest wykorzystywany w krokach pipeline. Domyślnie znajduje się tam wartość „**/*.sln”, która spowoduje, że build będzie wykonywany dla pierwszego pliku solution znalezionego w repozytorium. Tutaj musimy zmienić to, aby był brany pod uwagę plik solution z folderu project1.
Dla drugiego projektu przygotowałem bardzo podobny plik azure-pipelines.yml:
# ASP.NET Core (.NET Framework) | |
# Build and test ASP.NET Core projects targeting the full .NET Framework. | |
# Add steps that publish symbols, save build artifacts, and more: | |
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core | |
trigger: | |
branches: | |
include: | |
- master | |
paths: | |
include: | |
- project2/* | |
exclude: | |
- project2/azure-pipelines.yml | |
pool: | |
vmImage: 'windows-latest' | |
variables: | |
solution: 'project2/*.sln' | |
buildPlatform: 'Any CPU' | |
buildConfiguration: 'Release' | |
steps: | |
- task: NuGetToolInstaller@1 | |
- task: NuGetCommand@2 | |
inputs: | |
restoreSolution: '$(solution)' | |
- task: VSBuild@1 | |
inputs: | |
solution: '$(solution)' | |
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' | |
platform: '$(buildPlatform)' | |
configuration: '$(buildConfiguration)' |
Działanie path filters
Mając już przygotowane pliki yml, dodałem w Azure DevOps dwa pipeline na podstawie tych plików. Następnie wykonałem w repozytorium dwie zmiany. Po jednej dla każdego z projektów, które następnie wrzuciłem jako oddzielne commity do branch master:
W efekcie wykonały się oba pipeline wyzwalane poszczególnymi zmianami dla danego projektu:
Ładnie widać, że filtrowanie działa i zmiana w jednym projekcie powoduje wykonanie pipeline tylko dla tego projektu. Czyli mamy to, o co nam chodziło 🙂
Przykład
Na githubie znajduje się przykład do tego wpisu – https://github.com/danielplawgo/AzureDevOpsPathFilter. Znajdują się w nim oba pliki azure-pipelines.yml. Aby przetestować działanie path filters, potrzebujesz przerzucić kod do repozytorium w Azure DevOps i skonfigurować dwa pipeline dla każdego z plików azure-pipelines.yml.
Podsumowanie
Jednym z problemów podejścia monorepo jest optymalizacja budowania projektów, które znajdują się w repozytorium. Na szczęście w Azure DevOps jesteśmy w stanie ten problem bardzo łatwo obejść za pomocą path filters.
1 thought on “Azure DevOps path filters”