diff --git a/.github/actions/build-and-test/action.yml b/.github/actions/build-and-test/action.yml index fc252d4a..cfe26305 100644 --- a/.github/actions/build-and-test/action.yml +++ b/.github/actions/build-and-test/action.yml @@ -40,10 +40,10 @@ runs: - name: Determine Version uses: gittools/actions/gitversion/execute@v4.5.0 - - name: Setup .NET 8.x SDK + - name: Setup .NET 10.x SDK uses: actions/setup-dotnet@v5 with: - dotnet-version: 8.x + dotnet-version: 10.x - name: NuGet Cache uses: actions/cache@v5 diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d890d1..440f6515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning v2.0.0](https://semver.org/spec - Add dotnet install step to CI/CD - Upgrade .NET version to 10 - Organize ports for apps, containers, services +- Run sonar scan only on auth service CI, skip other services +- Ignore docker compose file for azure devops CI +- Skip CI if updates was only in MD files (trigger update) +- DockerFiles optimization for faster builds +- Split CI by separate Jobs to speed up builds +- Use Docker cache in CI +- Transform Frontend URL by using volume mount and configmaps ## [1.0.0] - 18-Apr-2026 diff --git a/azure-pipelines/build/build-auth.yml b/azure-pipelines/build/build-auth.yml index a2bddb1d..a32c243f 100644 --- a/azure-pipelines/build/build-auth.yml +++ b/azure-pipelines/build/build-auth.yml @@ -11,7 +11,7 @@ pr: none variables: - name: appName - value: 'AuthorizationAPI' + value: 'Auth' - name: System.Debug value: 'false' @@ -19,24 +19,33 @@ pool: vmImage: 'ubuntu-latest' stages: - - stage: 'Build_${{ variables.appName }}' - displayName: 'Build_${{ variables.appName }}' + - stage: 'Docker_Push_${{ variables.appName }}' + displayName: 'Docker_Push_${{ variables.appName }}' jobs: - template: ../templates/docker-build-push-jobs.yml parameters: - JobName: 'Build_${{ variables.appName }}' - solution: '$(System.DefaultWorkingDirectory)/EventTriangleAPI.sln' - buildConfiguration: 'Release' - backendProjectPath: '$(System.DefaultWorkingDirectory)/src/authorization/EventTriangleAPI.Authorization.Presentation' - shouldRunUnitTests: false - shouldRunIntegrationTests: true - integrationTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/authorization/EventTriangleAPI.Authorization.IntegrationTests/EventTriangleAPI.Authorization.IntegrationTests.csproj' + JobName: 'Docker_Push_${{ variables.appName }}' dockerRegistryUrl: 'docker.io/kaminome' - dockerBuildParameterUrl: 'https://auth-eventtriangle.razumovsky.me/' imageRepository: 'auth-service' dockerfilePath: '$(System.DefaultWorkingDirectory)/src/authorization/Dockerfile' dockerServiceConnection: 'Docker_Hub_Connection' shouldPushToAcr: true + shouldPushToDockerHub: true acrRegistryUrl: 'acrsharedd01.azurecr.io' acrServiceConnection: 'Azure_ACR_Connection' workingDirectoryForDocker: '$(System.DefaultWorkingDirectory)/src' + + - stage: 'Build_Test_Scan_${{ variables.appName }}' + displayName: 'Build_Test_Scan_${{ variables.appName }}' + jobs: + - template: ../templates/build-test-scan-jobs.yml + parameters: + JobName: 'Build_${{ variables.appName }}' + solution: '$(System.DefaultWorkingDirectory)/EventTriangleAPI.sln' + buildConfiguration: 'Release' + backendProjectPath: '$(System.DefaultWorkingDirectory)/src/authorization/EventTriangleAPI.Authorization.Presentation' + shouldRunUnitTests: false + shouldRunIntegrationTests: true + integrationTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/authorization/EventTriangleAPI.Authorization.IntegrationTests/EventTriangleAPI.Authorization.IntegrationTests.csproj' + sonarCloudEnabled: true + sonarCloudServiceConnection: 'SonarCloud_Service_Connection' diff --git a/azure-pipelines/build/build-consumer.yml b/azure-pipelines/build/build-consumer.yml index 1cb23e1e..3776db84 100644 --- a/azure-pipelines/build/build-consumer.yml +++ b/azure-pipelines/build/build-consumer.yml @@ -11,7 +11,7 @@ pr: none variables: - name: appName - value: 'ConsumerAPI' + value: 'Consumer' - name: System.Debug value: 'false' @@ -19,24 +19,33 @@ pool: vmImage: 'ubuntu-latest' stages: - - stage: 'Build_${{ variables.appName }}' - displayName: 'Build_${{ variables.appName }}' + - stage: 'Docker_Push_${{ variables.appName }}' + displayName: 'Docker_Push_${{ variables.appName }}' jobs: - template: ../templates/docker-build-push-jobs.yml parameters: - JobName: 'Build_${{ variables.appName }}' - solution: '$(System.DefaultWorkingDirectory)/EventTriangleAPI.sln' - buildConfiguration: 'Release' - backendProjectPath: '$(System.DefaultWorkingDirectory)/src/consumer/EventTriangleAPI.Consumer.Presentation' - unitTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/consumer/EventTriangleAPI.Consumer.UnitTests/EventTriangleAPI.Consumer.UnitTests.csproj' - shouldRunUnitTests: true - shouldRunIntegrationTests: true - integrationTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/consumer/EventTriangleAPI.Consumer.IntegrationTests/EventTriangleAPI.Consumer.IntegrationTests.csproj' + JobName: 'Docker_Push_${{ variables.appName }}' dockerRegistryUrl: 'docker.io/kaminome' imageRepository: 'consumer-service' dockerfilePath: '$(System.DefaultWorkingDirectory)/src/consumer/Dockerfile' dockerServiceConnection: 'Docker_Hub_Connection' + shouldPushToDockerHub: true shouldPushToAcr: true acrRegistryUrl: 'acrsharedd01.azurecr.io' acrServiceConnection: 'Azure_ACR_Connection' workingDirectoryForDocker: '$(System.DefaultWorkingDirectory)/src' + + - stage: 'Build_Test_Scan_${{ variables.appName }}' + displayName: 'Build_Test_Scan_${{ variables.appName }}' + jobs: + - template: ../templates/build-test-scan-jobs.yml + parameters: + JobName: 'Build_Test_Scan_${{ variables.appName }}' + solution: '$(System.DefaultWorkingDirectory)/EventTriangleAPI.sln' + buildConfiguration: 'Release' + backendProjectPath: '$(System.DefaultWorkingDirectory)/src/consumer/EventTriangleAPI.Consumer.Presentation' + unitTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/consumer/EventTriangleAPI.Consumer.UnitTests/EventTriangleAPI.Consumer.UnitTests.csproj' + shouldRunUnitTests: true + shouldRunIntegrationTests: true + integrationTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/consumer/EventTriangleAPI.Consumer.IntegrationTests/EventTriangleAPI.Consumer.IntegrationTests.csproj' + sonarCloudEnabled: false diff --git a/azure-pipelines/build/build-sender.yml b/azure-pipelines/build/build-sender.yml index b5f54900..e1da39fb 100644 --- a/azure-pipelines/build/build-sender.yml +++ b/azure-pipelines/build/build-sender.yml @@ -11,7 +11,7 @@ pr: none variables: - name: appName - value: 'SenderAPI' + value: 'Sender' - name: System.Debug value: 'false' @@ -19,24 +19,33 @@ pool: vmImage: 'ubuntu-latest' stages: - - stage: 'Build_${{ variables.appName }}' - displayName: 'Build_${{ variables.appName }}' + - stage: 'Docker_Push_${{ variables.appName }}' + displayName: 'Docker_Push_${{ variables.appName }}' jobs: - template: ../templates/docker-build-push-jobs.yml parameters: - JobName: 'Build_${{ variables.appName }}' - solution: '$(System.DefaultWorkingDirectory)/EventTriangleAPI.sln' - buildConfiguration: 'Release' - backendProjectPath: '$(System.DefaultWorkingDirectory)/src/sender/EventTriangleAPI.Sender.Presentation' - unitTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/sender/EventTriangleAPI.Sender.UnitTests/EventTriangleAPI.Sender.UnitTests.csproj' - shouldRunIntegrationTests: true - shouldRunUnitTests: true - integrationTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/sender/EventTriangleAPI.Sender.IntegrationTests/EventTriangleAPI.Sender.IntegrationTests.csproj' + JobName: 'Docker_Push_${{ variables.appName }}' dockerRegistryUrl: 'docker.io/kaminome' imageRepository: 'sender-service' dockerfilePath: '$(System.DefaultWorkingDirectory)/src/sender/Dockerfile' dockerServiceConnection: 'Docker_Hub_Connection' + shouldPushToDockerHub: true shouldPushToAcr: true acrRegistryUrl: 'acrsharedd01.azurecr.io' acrServiceConnection: 'Azure_ACR_Connection' workingDirectoryForDocker: '$(System.DefaultWorkingDirectory)/src' + + - stage: 'Build_Test_Scan_${{ variables.appName }}' + displayName: 'Build_Test_Scan_${{ variables.appName }}' + jobs: + - template: ../templates/build-test-scan-jobs.yml + parameters: + JobName: 'Build_Test_Scan_${{ variables.appName }}' + solution: '$(System.DefaultWorkingDirectory)/EventTriangleAPI.sln' + buildConfiguration: 'Release' + backendProjectPath: '$(System.DefaultWorkingDirectory)/src/sender/EventTriangleAPI.Sender.Presentation' + unitTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/sender/EventTriangleAPI.Sender.UnitTests/EventTriangleAPI.Sender.UnitTests.csproj' + shouldRunIntegrationTests: true + shouldRunUnitTests: true + integrationTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/sender/EventTriangleAPI.Sender.IntegrationTests/EventTriangleAPI.Sender.IntegrationTests.csproj' + sonarCloudEnabled: false diff --git a/azure-pipelines/pr-validation/pr-validation-auth.yml b/azure-pipelines/pr-validation/pr-validation-auth.yml index b76d17d4..8b2a5b2e 100644 --- a/azure-pipelines/pr-validation/pr-validation-auth.yml +++ b/azure-pipelines/pr-validation/pr-validation-auth.yml @@ -1,24 +1,51 @@ trigger: none pr: + autoCancel: true branches: include: - develop - main + paths: include: - - azure-pipelines - - platform - - helm - - cloudflare - - src/authorization/EventTriangleAPI.Authorization.BusinessLogic - - src/authorization/EventTriangleAPI.Authorization.Presentation - - src/shared/EventTriangleAPI.Shared.Application - - src/shared/EventTriangleAPI.Shared.DTO + # Auth service (ALL layers) + - src/authorization/** + + # Shared code used by auth + - src/shared/** + + # Pipeline itself (so changes validate) + - azure-pipelines/** + - azure-pipelines/templates/** + + exclude: + # Docs + - '**/*.md' + + # Docker compose (global/dev only) + - '**/docker-compose*.yml' + - '**/docker-compose*.yaml' + + # Ignore other services explicitly + - src/consumer/** + - src/sender/** + + # Helm & infra (unless auth-specific) + - helm/** + - platform/** + - cloudflare/** + - docs/** + - kubernetes/** + - img/** + - terraform/** + - terraform-azdo-libraries/** + - .github/** + - LICENSE variables: - name: appName - value: 'AuthorizationAPI' + value: 'Auth' - name: System.Debug value: 'false' @@ -31,18 +58,12 @@ stages: jobs: - template: ../templates/docker-build-push-jobs.yml parameters: - JobName: 'PR_Validation_${{ variables.appName }}' - solution: '$(System.DefaultWorkingDirectory)/EventTriangleAPI.sln' - buildConfiguration: 'Release' - backendProjectPath: '$(System.DefaultWorkingDirectory)/src/authorization/EventTriangleAPI.Authorization.Presentation' - shouldRunUnitTests: false - shouldRunIntegrationTests: true - integrationTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/authorization/EventTriangleAPI.Authorization.IntegrationTests/EventTriangleAPI.Authorization.IntegrationTests.csproj' + JobName: 'Docker_Push_${{ variables.appName }}' dockerRegistryUrl: 'docker.io/kaminome' - dockerBuildParameterUrl: 'https://auth-eventtriangle.razumovsky.me/' imageRepository: 'auth-service' dockerfilePath: '$(System.DefaultWorkingDirectory)/src/authorization/Dockerfile' dockerServiceConnection: 'Docker_Hub_Connection' + shouldPushToDockerHub: true shouldPushToAcr: true acrRegistryUrl: 'acrsharedd01.azurecr.io' acrServiceConnection: 'Azure_ACR_Connection' diff --git a/azure-pipelines/pr-validation/pr-validation-consumer.yml b/azure-pipelines/pr-validation/pr-validation-consumer.yml index 162df5df..f84543af 100644 --- a/azure-pipelines/pr-validation/pr-validation-consumer.yml +++ b/azure-pipelines/pr-validation/pr-validation-consumer.yml @@ -1,28 +1,51 @@ trigger: none pr: + autoCancel: true branches: include: - develop - main + paths: include: - - azure-pipelines - - platform - - helm - - cloudflare - - src/consumer/EventTriangleAPI.Consumer.Application - - src/consumer/EventTriangleAPI.Consumer.BusinessLogic - - src/consumer/EventTriangleAPI.Consumer.Domain - - src/consumer/EventTriangleAPI.Consumer.Presentation - - src/consumer/EventTriangleAPI.Consumer.IntegrationTests - - src/consumer/EventTriangleAPI.Consumer.UnitTests - - src/shared/EventTriangleAPI.Shared.Application - - src/shared/EventTriangleAPI.Shared.DTO + # Consumer service (ALL layers) + - src/consumer/** + + # Shared code used by auth + - src/shared/** + + # Pipeline itself (so changes validate) + - azure-pipelines/** + - azure-pipelines/templates/** + + exclude: + # Docs + - '**/*.md' + + # Docker compose (global/dev only) + - '**/docker-compose*.yml' + - '**/docker-compose*.yaml' + + # Ignore other services explicitly + - src/authorization/** + - src/sender/** + + # Helm & infra (unless auth-specific) + - helm/** + - platform/** + - cloudflare/** + - docs/** + - kubernetes/** + - img/** + - terraform/** + - terraform-azdo-libraries/** + - .github/** + - LICENSE variables: - name: appName - value: 'ConsumerAPI' + value: 'Consumer' - name: System.Debug value: 'false' @@ -35,18 +58,12 @@ stages: jobs: - template: ../templates/docker-build-push-jobs.yml parameters: - JobName: 'PR_Validation_${{ variables.appName }}' - solution: '$(System.DefaultWorkingDirectory)/EventTriangleAPI.sln' - buildConfiguration: 'Release' - backendProjectPath: '$(System.DefaultWorkingDirectory)/src/consumer/EventTriangleAPI.Consumer.Presentation' - unitTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/consumer/EventTriangleAPI.Consumer.UnitTests/EventTriangleAPI.Consumer.UnitTests.csproj' - shouldRunUnitTests: true - shouldRunIntegrationTests: true - integrationTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/consumer/EventTriangleAPI.Consumer.IntegrationTests/EventTriangleAPI.Consumer.IntegrationTests.csproj' + JobName: 'Docker_Push_${{ variables.appName }}' dockerRegistryUrl: 'docker.io/kaminome' imageRepository: 'consumer-service' dockerfilePath: '$(System.DefaultWorkingDirectory)/src/consumer/Dockerfile' dockerServiceConnection: 'Docker_Hub_Connection' + shouldPushToDockerHub: true shouldPushToAcr: true acrRegistryUrl: 'acrsharedd01.azurecr.io' acrServiceConnection: 'Azure_ACR_Connection' diff --git a/azure-pipelines/pr-validation/pr-validation-sender.yml b/azure-pipelines/pr-validation/pr-validation-sender.yml index f567d09f..fd265e2c 100644 --- a/azure-pipelines/pr-validation/pr-validation-sender.yml +++ b/azure-pipelines/pr-validation/pr-validation-sender.yml @@ -1,28 +1,51 @@ trigger: none pr: + autoCancel: true branches: include: - develop - main + paths: include: - - azure-pipelines - - platform - - helm - - cloudflare - - src/sender/EventTriangleAPI.Sender.Application - - src/sender/EventTriangleAPI.Sender.BusinessLogic - - src/sender/EventTriangleAPI.Sender.Domain - - src/sender/EventTriangleAPI.Sender.Presentation - - src/sender/EventTriangleAPI.Sender.IntegrationTests - - src/sender/EventTriangleAPI.Sender.UnitTests - - src/shared/EventTriangleAPI.Shared.Application - - src/shared/EventTriangleAPI.Shared.DTO + # Sender service (ALL layers) + - src/sender/** + + # Shared code used by auth + - src/shared/** + + # Pipeline itself (so changes validate) + - azure-pipelines/** + - azure-pipelines/templates/** + + exclude: + # Docs + - '**/*.md' + + # Docker compose (global/dev only) + - '**/docker-compose*.yml' + - '**/docker-compose*.yaml' + + # Ignore other services explicitly + - src/consumer/** + - src/authorization/** + + # Helm & infra (unless auth-specific) + - helm/** + - platform/** + - cloudflare/** + - docs/** + - kubernetes/** + - img/** + - terraform/** + - terraform-azdo-libraries/** + - .github/** + - LICENSE variables: - name: appName - value: 'SenderAPI' + value: 'Sender' - name: System.Debug value: 'false' @@ -35,18 +58,12 @@ stages: jobs: - template: ../templates/docker-build-push-jobs.yml parameters: - JobName: 'PR_Validation_${{ variables.appName }}' - solution: '$(System.DefaultWorkingDirectory)/EventTriangleAPI.sln' - buildConfiguration: 'Release' - backendProjectPath: '$(System.DefaultWorkingDirectory)/src/sender/EventTriangleAPI.Sender.Presentation' - unitTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/sender/EventTriangleAPI.Sender.UnitTests/EventTriangleAPI.Sender.UnitTests.csproj' - shouldRunIntegrationTests: true - shouldRunUnitTests: true - integrationTestsProjectPath: '$(System.DefaultWorkingDirectory)/src/sender/EventTriangleAPI.Sender.IntegrationTests/EventTriangleAPI.Sender.IntegrationTests.csproj' + JobName: 'Docker_Push_${{ variables.appName }}' dockerRegistryUrl: 'docker.io/kaminome' imageRepository: 'sender-service' dockerfilePath: '$(System.DefaultWorkingDirectory)/src/sender/Dockerfile' dockerServiceConnection: 'Docker_Hub_Connection' + shouldPushToDockerHub: true shouldPushToAcr: true acrRegistryUrl: 'acrsharedd01.azurecr.io' acrServiceConnection: 'Azure_ACR_Connection' diff --git a/azure-pipelines/templates/build-test-scan-jobs.yml b/azure-pipelines/templates/build-test-scan-jobs.yml new file mode 100644 index 00000000..d9ea4082 --- /dev/null +++ b/azure-pipelines/templates/build-test-scan-jobs.yml @@ -0,0 +1,160 @@ +parameters: + - name: JobName + displayName: 'Job Name' + type: string + + - name: solution + displayName: 'Solution file path' + type: string + + - name: buildConfiguration + displayName: 'Build Configuration' + default: 'Release' + type: string + + - name: backendProjectPath + displayName: 'Backend project path' + type: string + + - name: unitTestsProjectPath + displayName: 'Unit tests project path' + default: '' + type: string + + - name: shouldRunUnitTests + displayName: 'Flag should run unit tests' + default: true + type: boolean + + - name: shouldRunIntegrationTests + displayName: 'Flag should run integration tests' + type: boolean + + - name: integrationTestsProjectPath + displayName: 'Integration tests project path' + type: string + + - name: gitVersionVersion + displayName: 'GitVersion version' + default: '6.x' + type: string + + - name: dotnetSdkVersion + type: string + default: '10.x' + + - name: sonarCloudEnabled + type: boolean + default: false + + - name: sonarCloudServiceConnection + type: string + default: SonarCloud_Service_Connection + +jobs: + - job: ${{ parameters.JobName }} + displayName: ${{ parameters.JobName }} + steps: + - checkout: self + fetchDepth: 0 + + - task: UseDotNet@2 + displayName: 'Install .NET SDK ${{ parameters.dotnetSdkVersion }}' + condition: succeeded() + inputs: + packageType: 'sdk' + version: '${{ parameters.dotnetSdkVersion }}' + + - task: gitversion-setup@4.5.0 + displayName: 'GitVersion Setup' + condition: succeeded() + inputs: + versionSpec: ${{ parameters.gitVersionVersion }} + + - task: gitversion-execute@4.5.0 + name: version_step # step id used as a reference for output values + displayName: Determine Version + condition: succeeded() + + - bash: echo $Action$BuildVersion + displayName: 'Set Build Version' + condition: succeeded() + env: + Action: '##vso[build.updatebuildnumber]' + BuildVersion: $(version_step.semVer) + + - pwsh: | + Write-Host "=== ENV VARIABLES ===" + Get-ChildItem Env: | Sort-Object Name + + Write-Host "=== PIPELINE VARIABLES ===" + Write-Host "FullSemVer: $(version_step.FullSemVer)" + Write-Host "SemVer: $(version_step.SemVer)" + Write-Host "ShortSha: $(version_step.ShortSha)" + Write-Host "Build Source branch: $env:BUILD_SOURCEBRANCHNAME" + displayName: Debug environment + condition: succeeded() + + - task: DotNetCoreCLI@2 + displayName: 'Dotnet restore' + condition: succeeded() + inputs: + command: 'restore' + projects: '${{ parameters.solution }}' + arguments: '--verbosity minimal' + nugetConfigPath: 'nuget.config' + + - task: SonarCloudPrepare@4 + condition: and( + succeeded(), + ${{ parameters.sonarCloudEnabled }}, + eq(variables['Build.SourceBranchName'], 'main')) + inputs: + SonarCloud: ${{ parameters.sonarCloudServiceConnection }} + organization: event-triangle + scannerMode: dotnet + projectKey: event-triangle-api + projectName: eventtriangleapi + extraProperties: | + sonar.cs.vstest.reportsPaths=$(Agent.TempDirectory)/**/*.trx + sonar.cs.opencover.reportsPaths=$(Agent.TempDirectory)/**/*opencover.xml + sonar.exclusions=**/Properties/**, **/bin/**, **/obj/**, **/Migrations/** + + - task: DotNetCoreCLI@2 + displayName: 'Build solution' + condition: succeeded() + inputs: + command: 'build' + projects: '${{ parameters.solution }}' + arguments: '-c ${{ parameters.buildConfiguration }} -p:Version=$(version_step.semVer) --no-restore' + + - task: SonarCloudAnalyze@4 + condition: and( + succeeded(), + ${{ parameters.sonarCloudEnabled }}, + eq(variables['Build.SourceBranchName'], 'main')) + inputs: {} + + - task: SonarCloudPublish@4 + condition: and( + succeeded(), + ${{ parameters.sonarCloudEnabled }}, + eq(variables['Build.SourceBranchName'], 'main')) + inputs: + pollingTimeoutSec: "300" + + - task: DotnetCoreCLI@2 + displayName: 'Run unit tests' + condition: and(succeeded(), eq(${{ parameters.shouldRunUnitTests }}, true)) + inputs: + command: 'test' + projects: '${{ parameters.unitTestsProjectPath }}' + arguments: '--no-build --configuration ${{ parameters.buildConfiguration }} --collect "Code Coverage"' + + - task: DotNetCoreCLI@2 + displayName: 'Run integration tests' + condition: and(succeeded(), eq(${{ parameters.shouldRunIntegrationTests }}, true)) + inputs: + command: 'test' + projects: '${{ parameters.integrationTestsProjectPath }}' + arguments: '--no-build --configuration ${{ parameters.buildConfiguration }} --collect "Code Coverage"' diff --git a/azure-pipelines/templates/docker-build-push-jobs.yml b/azure-pipelines/templates/docker-build-push-jobs.yml index 0de6f06e..c1d0f213 100644 --- a/azure-pipelines/templates/docker-build-push-jobs.yml +++ b/azure-pipelines/templates/docker-build-push-jobs.yml @@ -8,37 +8,6 @@ parameters: default: '$(System.DefaultWorkingDirectory)/src' type: string - - name: solution - displayName: 'Solution file path' - type: string - - - name: buildConfiguration - displayName: 'Build Configuration' - default: 'Release' - type: string - - - name: backendProjectPath - displayName: 'Backend project path' - type: string - - - name: unitTestsProjectPath - displayName: 'Unit tests project path' - default: '' - type: string - - - name: shouldRunUnitTests - displayName: 'Flag should run unit tests' - default: true - type: boolean - - - name: shouldRunIntegrationTests - displayName: 'Flag should run integration tests' - type: boolean - - - name: integrationTestsProjectPath - displayName: 'Integration tests project path' - type: string - - name: gitVersionVersion displayName: 'GitVersion version' default: '6.x' @@ -48,6 +17,10 @@ parameters: type: string displayName: 'DockerHub Registry URL. For Docker Hub it is: docker.io/kaminome.' + - name: shouldPushToDockerHub + type: boolean + default: false + - name: shouldPushToAcr type: boolean default: false @@ -65,22 +38,13 @@ parameters: type: string - name: dockerServiceConnection - displayName: 'Docker container registry service connection in Azure DevOps.' + displayName: 'DockerHub service connection in Azure DevOps.' type: string - name: acrServiceConnection displayName: 'Azure container registry service connection in Azure DevOps.' type: string - - name: dockerBuildParameterUrl - displayName: 'Build parameter for Docker: Base URL' - default: 'https://auth.eventtriangle.razumovsky.me/' - type: string - - - name: dotnetSdkVersion - type: string - default: '10.x' - jobs: - job: ${{ parameters.JobName }} displayName: ${{ parameters.JobName }} @@ -88,23 +52,20 @@ jobs: - checkout: self fetchDepth: 0 - - task: UseDotNet@2 - displayName: 'Install .NET SDK ${{ parameters.dotnetSdkVersion }}' - inputs: - packageType: 'sdk' - version: '${{ parameters.dotnetSdkVersion }}' - - task: gitversion-setup@4.5.0 displayName: 'GitVersion Setup' + condition: succeeded() inputs: versionSpec: ${{ parameters.gitVersionVersion }} - task: gitversion-execute@4.5.0 name: version_step # step id used as a reference for output values displayName: Determine Version + condition: succeeded() - bash: echo $Action$BuildVersion displayName: 'Set Build Version' + condition: succeeded() env: Action: '##vso[build.updatebuildnumber]' BuildVersion: $(version_step.semVer) @@ -117,82 +78,19 @@ jobs: Write-Host "FullSemVer: $(version_step.FullSemVer)" Write-Host "SemVer: $(version_step.SemVer)" Write-Host "ShortSha: $(version_step.ShortSha)" - - Write-Host "=== PIPELINE VARIABLES ===" Write-Host "Build Source branch: $env:BUILD_SOURCEBRANCHNAME" displayName: Debug environment - - - task: PowerShell@2 - displayName: 'Debug branch condition' - condition: ne(variables['Build.SourceBranchName'], 'main') - inputs: - targetType: 'inline' - script: | - Write-Host "Running because branch is NOT main" - Write-Host "Branch: $env:BUILD_SOURCEBRANCHNAME" - - - task: DotNetCoreCLI@2 - displayName: 'Dotnet restore' - inputs: - command: 'restore' - projects: '${{ parameters.solution }}' - arguments: '--verbosity minimal' - nugetConfigPath: 'nuget.config' - - - task: SonarCloudPrepare@4 - condition: eq(variables['Build.SourceBranchName'], 'main') - inputs: - SonarCloud: SonarCloud_Service_Connection - organization: event-triangle - scannerMode: dotnet - projectKey: event-triangle-api - projectName: eventtriangleapi - extraProperties: | - sonar.cs.vstest.reportsPaths=$(Agent.TempDirectory)/**/*.trx - sonar.cs.opencover.reportsPaths=$(Agent.TempDirectory)/**/*opencover.xml - sonar.exclusions=**/Properties/**, **/bin/**, **/obj/**, **/Migrations/** - - - task: DotNetCoreCLI@2 - displayName: 'Build solution' - inputs: - command: 'build' - projects: '${{ parameters.solution }}' - arguments: '-c ${{ parameters.buildConfiguration }} -p:Version=$(version_step.semVer) --no-restore' - - - task: SonarCloudAnalyze@4 - condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'main')) - inputs: {} - - - task: SonarCloudPublish@4 - condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'main')) - inputs: - pollingTimeoutSec: "300" - - - task: DotnetCoreCLI@2 - displayName: 'Run unit tests' - condition: and(succeeded(), eq(${{ parameters.shouldRunUnitTests }}, true)) - inputs: - command: 'test' - projects: '${{ parameters.unitTestsProjectPath }}' - arguments: '--no-build --configuration ${{ parameters.buildConfiguration }} --collect "Code Coverage"' - - - task: DotNetCoreCLI@2 - displayName: 'Run integration tests' - condition: and(succeeded(), eq(${{ parameters.shouldRunIntegrationTests }}, true)) - inputs: - command: 'test' - projects: '${{ parameters.integrationTestsProjectPath }}' - arguments: '--no-build --configuration ${{ parameters.buildConfiguration }} --collect "Code Coverage"' + condition: succeeded() - task: PowerShell@2 displayName: 'Build Docker Image' + condition: succeeded() inputs: targetType: 'filePath' filePath: '$(System.DefaultWorkingDirectory)/scripts/Build-Docker.ps1' arguments: "-DockerRegistryUrl ${{ parameters.dockerRegistryUrl }} -ImageRepository ${{ parameters.imageRepository }} -AcrRegistryUrl ${{ parameters.acrRegistryUrl }} - -DockerBuildParameterUrl ${{ parameters.dockerBuildParameterUrl }} -DockerfilePath ${{ parameters.dockerfilePath }} -GitVersion $(version_step.semVer) -WorkingDirectory ${{ parameters.workingDirectoryForDocker }} @@ -202,6 +100,7 @@ jobs: - task: Docker@2 displayName: 'Push to DockerHub' + condition: and(succeeded(), eq(${{ parameters.shouldPushToDockerHub }}, true)) inputs: command: push containerRegistry: ${{ parameters.dockerServiceConnection }} diff --git a/scripts/Build-Docker.ps1 b/scripts/Build-Docker.ps1 index 464c4cd6..1ce5a8fc 100644 --- a/scripts/Build-Docker.ps1 +++ b/scripts/Build-Docker.ps1 @@ -8,9 +8,6 @@ param ( [Parameter(Mandatory = $true)] [string]$AcrRegistryUrl, - [Parameter(Mandatory = $true)] - [string]$DockerBuildParameterUrl, - [Parameter(Mandatory = $true)] [string]$DockerfilePath, @@ -24,6 +21,19 @@ param ( [string]$WorkingDirectory ) +$ErrorActionPreference = "Stop" + +Write-Host "================================================================================" + +docker --version + +Write-Host "================================================================================" + +# Variable to use modern Docker BuildKit +$env:DOCKER_BUILDKIT = "1" + +$InitDirectory = Get-Location + Write-Output "Changing directory to $WorkingDirectory" Set-Location $WorkingDirectory @@ -37,6 +47,9 @@ $ACR_LATEST_VERSION_IMAGE = "$AcrRegistryUrl/$ImageRepository`:latest" $ACR_SHA_TAG = "$AcrRegistryUrl/$ImageRepository`:$GitVersion-$CommitSha" # Output image tags + +Write-Host "================================================================================" + Write-Output "DOCKERHUB_GIT_VERSION_IMAGE: $GIT_VERSION_IMAGE" Write-Output "DOCKERHUB_GIT_LATEST_VERSION_IMAGE: $LATEST_VERSION_IMAGE" Write-Output "DOCKERHUB_SHA_VERSION_IMAGE: $SHA_TAG" @@ -45,11 +58,22 @@ Write-Output "ACR_GIT_VERSION_IMAGE: $ACR_GIT_VERSION_IMAGE" Write-Output "ACR_LATEST_VERSION_IMAGE: $ACR_LATEST_VERSION_IMAGE" Write-Output "ACR_SHA_IMAGE: $ACR_SHA_TAG" +Write-Host "================================================================================" + +# Try to pull cache image +docker pull "$LATEST_VERSION_IMAGE" 2>$null + +$sw = [System.Diagnostics.Stopwatch]::StartNew() + # Build the Docker image -docker build --build-arg FRONT_API_URL="$DockerBuildParameterUrl" ` - --build-arg VERSION="$gitVersion" ` - -t "$GIT_VERSION_IMAGE" ` - -f "$DockerfilePath" . +docker buildx build --load ` + --cache-from "type=registry,ref=$LATEST_VERSION_IMAGE" ` + --cache-to "type=inline" ` + -t "$GIT_VERSION_IMAGE" ` + -f "$DockerfilePath" . + +$sw.Stop() +Write-Host "Docker build occupied: $($sw.Elapsed.TotalSeconds)" # Tag the images docker tag "$GIT_VERSION_IMAGE" "$LATEST_VERSION_IMAGE" @@ -61,12 +85,16 @@ docker tag "$GIT_VERSION_IMAGE" "$ACR_SHA_TAG" # List images docker image ls +Write-Host "Set location back to $InitDirectory..." + +Set-Location $InitDirectory + # EXAMPLE CALL #.\Build-Docker.ps1 ` # -DockerRegistryUrl "docker.io/kaminome"` # -ImageRepository "auth-service" ` # -AcrRegistryUrl "azuredevopsacrd01.azurecr.io" ` -# -DockerBuildParameterUrl "https://auth-eventtriangle.razumovsky.me/" ` # -DockerfilePath "E:\RiderProjects\02_DOTNET_PROJECTS\EventTriangleAPI\src\authorization\Dockerfile" ` # -GitVersion "1.0.0" ` +# -CommitSha "8e33ce9" ` # -WorkingDirectory "E:\RiderProjects\02_DOTNET_PROJECTS\EventTriangleAPI\src" diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 00000000..5e438fe7 --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,63 @@ +# ========================= +# Build outputs (.NET) +# ========================= +**/bin/ +**/obj/ + +# ========================= +# Node / frontend +# ========================= +**/node_modules/ +**/dist/ +**/build/ + +# ========================= +# IDE / editor +# ========================= +.vscode/ +.idea/ +*.suo +*.user +*.userosscache +*.sln.docstates +**/.editorconfig +*.sln +*.config + +# ========================= +# Git +# ========================= +.git/ +.gitignore +.gitattributes + +# ========================= +# Logs / temp +# ========================= +*.log +*.tmp +*.cache + +# ========================= +# OS files +# ========================= +.DS_Store +Thumbs.db + +# ========================= +# Tests (optional!) +# ========================= +**/*Tests/ +**/*.Test*/ + +# ========================= +# OTHER +# ========================= +**/bin +**/obj +**/.vs +**/.vscode +**/*.md +**/docker-compose* +.git +**/scripts diff --git a/src/authorization/Dockerfile b/src/authorization/Dockerfile index ac2cf3e7..215458b8 100644 --- a/src/authorization/Dockerfile +++ b/src/authorization/Dockerfile @@ -1,40 +1,74 @@ +####################################################################################################### +# BASE RUNTIME +####################################################################################################### FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base WORKDIR /app EXPOSE 8080 - ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true -FROM node:18.15.0-alpine AS angularBuild -WORKDIR /angular -COPY ["authorization/EventTriangle.Client/package.json", "EventTriangle.Client/"] -COPY ["authorization/EventTriangle.Client/package-lock.json", "EventTriangle.Client/"] -WORKDIR "EventTriangle.Client" +####################################################################################################### +# ANGULAR BUILD (CACHE OPTIMIZED) +####################################################################################################### +FROM node:18.15.0-alpine AS angular + +WORKDIR /app/client + +# 1. dependency layer (VERY STABLE) +COPY authorization/EventTriangle.Client/package.json ./ +COPY authorization/EventTriangle.Client/package-lock.json ./ +COPY authorization/EventTriangle.Client/angular.json ./ +COPY authorization/EventTriangle.Client/tsconfig*.json ./ + RUN npm ci -WORKDIR /angular -COPY ["authorization/EventTriangle.Client", "EventTriangle.Client/"] -RUN npm install -g @angular/cli@15.2.7 -WORKDIR "EventTriangle.Client" -ARG FRONT_API_URL -RUN sed -i "s|https://localhost:7000/|$FRONT_API_URL|" ./src/assets/config/config.json -RUN ng build --output-path "dist/client" - -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS publish + +# 2. source layer (changes often) +COPY authorization/EventTriangle.Client/src ./src +COPY authorization/EventTriangle.Client/src/assets ./assets +COPY authorization/EventTriangle.Client/src/favicon.png ./src/favicon.png + +RUN npm run build -- --configuration production --output-path dist/client + +####################################################################################################### +# DOTNET RESTORE LAYER (MOST IMPORTANT CACHE LAYER) +####################################################################################################### +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore WORKDIR /src -COPY ["authorization/EventTriangleAPI.Authorization.BusinessLogic/EventTriangleAPI.Authorization.BusinessLogic.csproj", "authorization/EventTriangleAPI.Authorization.BusinessLogic/"] -COPY ["authorization/EventTriangleAPI.Authorization.Domain/EventTriangleAPI.Authorization.Domain.csproj", "authorization/EventTriangleAPI.Authorization.Domain/"] -COPY ["authorization/EventTriangleAPI.Authorization.Persistence/EventTriangleAPI.Authorization.Persistence.csproj", "authorization/EventTriangleAPI.Authorization.Persistence/"] -COPY ["authorization/EventTriangleAPI.Authorization.Presentation/EventTriangleAPI.Authorization.Presentation.csproj", "authorization/EventTriangleAPI.Authorization.Presentation/"] -COPY ["authorization/EventTriangleAPI.Authorization.UnitTests/EventTriangleAPI.Authorization.UnitTests.csproj", "authorization/EventTriangleAPI.Authorization.UnitTests/"] -COPY ["shared/EventTriangleAPI.Shared.Application/EventTriangleAPI.Shared.Application.csproj", "shared/EventTriangleAPI.Shared.Application/"] -COPY ["shared/EventTriangleAPI.Shared.DTO/EventTriangleAPI.Shared.DTO.csproj", "shared/EventTriangleAPI.Shared.DTO/"] -RUN dotnet restore "authorization/EventTriangleAPI.Authorization.Presentation/EventTriangleAPI.Authorization.Presentation.csproj" -COPY . . -WORKDIR "authorization/EventTriangleAPI.Authorization.Presentation" + +# ONLY csproj files first (MAXIMUM CACHE HIT RATE) +WORKDIR /src + +COPY authorization/EventTriangleAPI.Authorization.BusinessLogic/*.csproj authorization/EventTriangleAPI.Authorization.BusinessLogic/ +COPY authorization/EventTriangleAPI.Authorization.Domain/*.csproj authorization/EventTriangleAPI.Authorization.Domain/ +COPY authorization/EventTriangleAPI.Authorization.Persistence/*.csproj authorization/EventTriangleAPI.Authorization.Persistence/ +COPY authorization/EventTriangleAPI.Authorization.Presentation/*.csproj authorization/EventTriangleAPI.Authorization.Presentation/ + +COPY shared/EventTriangleAPI.Shared.Application/*.csproj shared/EventTriangleAPI.Shared.Application/ +COPY shared/EventTriangleAPI.Shared.DTO/*.csproj shared/EventTriangleAPI.Shared.DTO/ + +RUN dotnet restore authorization/EventTriangleAPI.Authorization.Presentation/EventTriangleAPI.Authorization.Presentation.csproj + +####################################################################################################### +# DOTNET BUILD + PUBLISH +####################################################################################################### +FROM restore AS build + +# NOW source code (this invalidates only build, not restore) +COPY authorization/ authorization/ +COPY shared/ shared/ + +WORKDIR /src/authorization/EventTriangleAPI.Authorization.Presentation + ARG VERSION -RUN dotnet publish "EventTriangleAPI.Authorization.Presentation.csproj" -c Release -p:Version=$VERSION -o /app/publish /p:UseAppHost=false --no-cache +RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false + +####################################################################################################### +# FINAL IMAGE +####################################################################################################### FROM base AS final WORKDIR /app -COPY --from=publish /app/publish . -COPY --from=angularBuild "/angular/EventTriangle.Client/dist/client" wwwroot + +COPY --from=build /app/publish . +COPY --from=angular /app/client/dist/client wwwroot + ENTRYPOINT ["dotnet", "EventTriangleAPI.Authorization.Presentation.dll"] diff --git a/src/authorization/EventTriangle.Client/package.json b/src/authorization/EventTriangle.Client/package.json index 1794bfed..86007d48 100644 --- a/src/authorization/EventTriangle.Client/package.json +++ b/src/authorization/EventTriangle.Client/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "^15.2.5", - "@angular/cli": "~15.2.5", + "@angular/cli": "~15.2.7", "@angular/compiler-cli": "^15.2.0", "@types/jasmine": "~4.3.0", "jasmine-core": "~4.5.0", diff --git a/src/authorization/EventTriangle.Client/src/assets/.gitkeep b/src/authorization/EventTriangle.Client/src/assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/authorization/EventTriangleAPI.Authorization.IntegrationTests/EventTriangleAPI.Authorization.IntegrationTests.csproj b/src/authorization/EventTriangleAPI.Authorization.IntegrationTests/EventTriangleAPI.Authorization.IntegrationTests.csproj index 98746e54..8f5a19c6 100644 --- a/src/authorization/EventTriangleAPI.Authorization.IntegrationTests/EventTriangleAPI.Authorization.IntegrationTests.csproj +++ b/src/authorization/EventTriangleAPI.Authorization.IntegrationTests/EventTriangleAPI.Authorization.IntegrationTests.csproj @@ -1,4 +1,4 @@ - + net10.0 diff --git a/src/authorization/EventTriangleAPI.Authorization.Presentation/Program.cs b/src/authorization/EventTriangleAPI.Authorization.Presentation/Program.cs index dde6d803..dd271ed7 100644 --- a/src/authorization/EventTriangleAPI.Authorization.Presentation/Program.cs +++ b/src/authorization/EventTriangleAPI.Authorization.Presentation/Program.cs @@ -113,9 +113,10 @@ app.UseAuthorization(); -app.MapReverseProxy(); app.MapControllers(); +app.MapReverseProxy(); + app.MigrateDatabase(); app.Map(SpaRouting.Transactions, config => config.UseSpa(spa => spa.Options.SourcePath = "/wwwroot")); diff --git a/src/consumer/Dockerfile b/src/consumer/Dockerfile index 305c1f06..77bc7df5 100644 --- a/src/consumer/Dockerfile +++ b/src/consumer/Dockerfile @@ -9,14 +9,14 @@ COPY ["consumer/EventTriangleAPI.Consumer.BusinessLogic/EventTriangleAPI.Consume COPY ["consumer/EventTriangleAPI.Consumer.Domain/EventTriangleAPI.Consumer.Domain.csproj", "consumer/EventTriangleAPI.Consumer.Domain/"] COPY ["consumer/EventTriangleAPI.Consumer.Persistence/EventTriangleAPI.Consumer.Persistence.csproj", "consumer/EventTriangleAPI.Consumer.Persistence/"] COPY ["consumer/EventTriangleAPI.Consumer.Presentation/EventTriangleAPI.Consumer.Presentation.csproj", "consumer/EventTriangleAPI.Consumer.Presentation/"] -COPY ["consumer/EventTriangleAPI.Consumer.UnitTests/EventTriangleAPI.Consumer.UnitTests.csproj", "consumer/EventTriangleAPI.Consumer.UnitTests/"] +#COPY ["consumer/EventTriangleAPI.Consumer.UnitTests/EventTriangleAPI.Consumer.UnitTests.csproj", "consumer/EventTriangleAPI.Consumer.UnitTests/"] COPY ["shared/EventTriangleAPI.Shared.Application/EventTriangleAPI.Shared.Application.csproj", "shared/EventTriangleAPI.Shared.Application/"] COPY ["shared/EventTriangleAPI.Shared.DTO/EventTriangleAPI.Shared.DTO.csproj", "shared/EventTriangleAPI.Shared.DTO/"] RUN dotnet restore "consumer/EventTriangleAPI.Consumer.Presentation/EventTriangleAPI.Consumer.Presentation.csproj" COPY . . WORKDIR "consumer/EventTriangleAPI.Consumer.Presentation" ARG VERSION -RUN dotnet publish "EventTriangleAPI.Consumer.Presentation.csproj" -c Release -p:Version=$VERSION -o /app/publish /p:UseAppHost=false --no-cache +RUN dotnet publish "EventTriangleAPI.Consumer.Presentation.csproj" -c Release -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app diff --git a/src/sender/Dockerfile b/src/sender/Dockerfile index f483175b..4e45b1be 100644 --- a/src/sender/Dockerfile +++ b/src/sender/Dockerfile @@ -1,5 +1,7 @@ FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base + WORKDIR /app + EXPOSE 8080 EXPOSE 8081 @@ -10,14 +12,14 @@ COPY ["sender/EventTriangleAPI.Sender.BusinessLogic/EventTriangleAPI.Sender.Busi COPY ["sender/EventTriangleAPI.Sender.Domain/EventTriangleAPI.Sender.Domain.csproj", "sender/EventTriangleAPI.Sender.Domain/"] COPY ["sender/EventTriangleAPI.Sender.Persistence/EventTriangleAPI.Sender.Persistence.csproj", "sender/EventTriangleAPI.Sender.Persistence/"] COPY ["sender/EventTriangleAPI.Sender.Presentation/EventTriangleAPI.Sender.Presentation.csproj", "sender/EventTriangleAPI.Sender.Presentation/"] -COPY ["sender/EventTriangleAPI.Sender.UnitTests/EventTriangleAPI.Sender.UnitTests.csproj", "sender/EventTriangleAPI.Sender.UnitTests/"] +#COPY ["sender/EventTriangleAPI.Sender.UnitTests/EventTriangleAPI.Sender.UnitTests.csproj", "sender/EventTriangleAPI.Sender.UnitTests/"] COPY ["shared/EventTriangleAPI.Shared.Application/EventTriangleAPI.Shared.Application.csproj", "shared/EventTriangleAPI.Shared.Application/"] COPY ["shared/EventTriangleAPI.Shared.DTO/EventTriangleAPI.Shared.DTO.csproj", "shared/EventTriangleAPI.Shared.DTO/"] RUN dotnet restore "sender/EventTriangleAPI.Sender.Presentation/EventTriangleAPI.Sender.Presentation.csproj" COPY . . WORKDIR "sender/EventTriangleAPI.Sender.Presentation" ARG VERSION -RUN dotnet publish "EventTriangleAPI.Sender.Presentation.csproj" -c Release -p:Version=$VERSION -o /app/publish /p:UseAppHost=false --no-cache +RUN dotnet publish "EventTriangleAPI.Sender.Presentation.csproj" -c Release -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app