From 973a9573ccae1d06fc9eb95f24a7cffd4762e3c7 Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:53:57 +0100 Subject: [PATCH] Task: Dependency track (#20670) * Generate BOM files on build * Upload BOM to Dependency Track * Move Backoffice BOM generation to right after install The build and/or pack steps are deleting files that are needed for the BOM to be generated properly. * Split the BOM uploads into different jobs * Fix wrong usage of parameters * Move order of dependency track stage * Fix wrong umbracoVersion value * Small fixes * Log curl response headers * Correct version sent to dependency track * Adjusted curl flags * Fix bom file path * Fix dotnet bom file name * Add Login UI to dependency track * Generate BOM for E2E Tests * Move dependency track stage * Move acceptance test .env generation to e2e install template Needed as the post install script is expecting this to exist. * Use major version if public release * Missing ')' * Reverted npm install command changes in static assets project --- build/azure-pipelines.yml | 81 ++++++++++++++++++++++++++++ build/nightly-E2E-setup-template.yml | 34 +++--------- build/templates/dependency-track.yml | 56 +++++++++++++++++++ build/templates/e2e-install.yml | 49 +++++++++++++++++ 4 files changed, 193 insertions(+), 27 deletions(-) create mode 100644 build/templates/dependency-track.yml create mode 100644 build/templates/e2e-install.yml diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 2246e09e7b..f2f18175fa 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -34,6 +34,10 @@ parameters: displayName: Upload API docs type: boolean default: false + - name: uploadDependencyTrack + displayName: Upload BOMs to Dependency Track + type: boolean + default: false - name: forceReleaseTestFilter displayName: Force to use the release test filters type: boolean @@ -103,6 +107,15 @@ stages: command: build projects: $(solution) arguments: "--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg" + - powershell: | + dotnet tool install --global CycloneDX + dotnet-CycloneDX $(solution) --output $(Build.ArtifactStagingDirectory)/bom --filename bom-dotnet.xml + displayName: 'Generate Backend BOM' + - powershell: | + npm install --global @cyclonedx/cyclonedx-npm + cyclonedx-npm -o $(Build.ArtifactStagingDirectory)\bom\bom-login.xml --ignore-npm-errors --verbose + displayName: Generate Login UI BOM + workingDirectory: src/Umbraco.Web.UI.Login - task: PublishPipelineArtifact@1 displayName: Publish nupkg inputs: @@ -113,6 +126,11 @@ stages: inputs: targetPath: $(Build.SourcesDirectory) artifactName: build_output + - task: PublishPipelineArtifact@1 + displayName: Publish Backend BOM + inputs: + targetPath: $(Build.ArtifactStagingDirectory)/bom + artifactName: bom-backend - job: B displayName: Build Bellissima Package @@ -124,6 +142,11 @@ stages: lfs: false, fetchDepth: 500 - template: templates/backoffice-install.yml + - powershell: | + npm install --global @cyclonedx/cyclonedx-npm + cyclonedx-npm -o $(Build.ArtifactStagingDirectory)/bom/bom-backoffice.xml --ignore-npm-errors --verbose + displayName: Generate Backoffice UI BOM + workingDirectory: src/Umbraco.Web.UI.Client - script: npm run build:for:npm displayName: Run build:for:npm workingDirectory: src/Umbraco.Web.UI.Client @@ -140,6 +163,35 @@ stages: inputs: targetPath: $(Build.ArtifactStagingDirectory)/npm artifactName: npm + - publish: $(Build.ArtifactStagingDirectory)/bom + artifact: bom-frontend + displayName: 'Publish Frontend BOM' + + - stage: E2E_BOM + displayName: E2E Tests BOM Generation + dependsOn: [] + jobs: + - job: + displayName: E2E Generate BOM + pool: + vmImage: "ubuntu-latest" + steps: + - checkout: self + submodules: false + lfs: false, + fetchDepth: 500 + - template: templates/e2e-install.yml + parameters: + nodeVersion: ${{ variables.nodeVersion }} + npm_config_cache: ${{ variables.npm_config_cache }} + - powershell: | + npm install --global @cyclonedx/cyclonedx-npm + cyclonedx-npm -o $(Build.ArtifactStagingDirectory)/bom/bom-e2e.xml --ignore-npm-errors --verbose + displayName: Generate E2E Tests BOM + workingDirectory: tests/Umbraco.Tests.AcceptanceTest + - publish: $(Build.ArtifactStagingDirectory)/bom + artifact: bom-e2e + displayName: 'Publish E2E BOM' - stage: Build_Docs condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.buildApiDocs}})) @@ -668,6 +720,34 @@ stages: ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }} DatabaseType: ${{ variables.DatabaseType }} + - stage: Dependency_Track + displayName: Dependency Track + dependsOn: + - Build + - E2E_BOM + condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.uploadDependencyTrack}})) + variables: + # Determine Umbraco version based on whether it's a public release or not. If public release, use major version, else use full NuGet package version. + umbracoVersion: $[ iif(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'], stageDependencies.Build.A.outputs['build.NBGV_NuGetPackageVersion']) ] + jobs: + - template: templates/dependency-track.yml + parameters: + projectName: "Umbraco-CMS" + umbracoVersion: $(umbracoVersion) + projects: + - name: "Backend" + artifact: "bom-backend" + bomFilePath: "bom-dotnet.xml" + - name: "Login" + artifact: "bom-backend" + bomFilePath: "bom-login.xml" + - name: "Backoffice" + artifact: "bom-frontend" + bomFilePath: "bom-backoffice.xml" + - name: "E2E" + artifact: "bom-e2e" + bomFilePath: "bom-e2e.xml" + ############################################### ## Release ############################################### @@ -874,3 +954,4 @@ stages: ContainerName: "$web" BlobPrefix: v$(umbracoMajorVersion)/ui-api CleanTargetBeforeCopy: true + diff --git a/build/nightly-E2E-setup-template.yml b/build/nightly-E2E-setup-template.yml index 8085561900..907ace6b72 100644 --- a/build/nightly-E2E-setup-template.yml +++ b/build/nightly-E2E-setup-template.yml @@ -26,38 +26,18 @@ steps: artifact: nupkg path: $(Agent.BuildDirectory)/app/nupkg - - task: NodeTool@0 - displayName: Use Node.js $(nodeVersion) - inputs: - versionSpec: $(nodeVersion) - - task: UseDotNet@2 displayName: Use .NET SDK from global.json inputs: useGlobalJson: true - - pwsh: | - "UMBRACO_USER_LOGIN=${{ parameters.PlaywrightUserEmail }} - UMBRACO_USER_PASSWORD=${{ parameters.PlaywrightPassword }} - URL=${{ parameters.ASPNETCORE_URLS }} - STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json - CONSOLE_ERRORS_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/console-errors.json" | Out-File .env - displayName: Generate .env - workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest - - # Cache and restore NPM packages - - task: Cache@2 - displayName: Cache NPM packages - inputs: - key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json' - restoreKeys: | - npm_e2e | "$(Agent.OS)" - npm_e2e - path: ${{ parameters.npm_config_cache }} - - - script: npm ci --no-fund --no-audit --prefer-offline - workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest - displayName: Restore NPM packages + - template: templates/e2e-install.yml + parameters: + nodeVersion: ${{ parameters.nodeVersion }} + npm_config_cache: ${{ parameters.npm_config_cache }} + PlaywrightUserEmail: ${{ parameters.PlaywrightUserEmail }} + PlaywrightPassword: ${{ parameters.PlaywrightPassword }} + ASPNETCORE_URLS: ${{ parameters.ASPNETCORE_URLS }} # Install Template - pwsh: | diff --git a/build/templates/dependency-track.yml b/build/templates/dependency-track.yml new file mode 100644 index 0000000000..74968b4616 --- /dev/null +++ b/build/templates/dependency-track.yml @@ -0,0 +1,56 @@ +parameters: + - name: projectName + type: string + - name: umbracoVersion + type: string + - name: projects + type: object + +jobs: +- job: Create_DT_Project + displayName: Create Dependency Track Project + steps: + - checkout: none + + - bash: | + project_id=$(curl --no-progress-meter -H "X-Api-Key: $(DT_API_KEY)" "$(DT_API_URL)/v1/project/lookup?name=${{ parameters.projectName }}&version=${{ parameters.umbracoVersion }}" | jq -r '.uuid') + if [ "$project_id" != "null" ] && [ -n "$project_id" ]; then + echo "Project '${{ parameters.projectName }}' with version '${{ parameters.umbracoVersion }}' already exists (ID: $project_id)." + else + project_id=$(curl --no-progress-meter \ + -X PUT "$(DT_API_URL)/v1/project" \ + -H "X-Api-Key: $(DT_API_KEY)" \ + -H "Content-Type: application/json" \ + -d '{"name": "${{ parameters.projectName }}", "version": "${{ parameters.umbracoVersion }}", "collectionLogic": "AGGREGATE_DIRECT_CHILDREN"}' \ + | jq -r '.uuid') + if [ -z "$project_id" ] || [ "$project_id" == "null" ]; then + echo "Failed to create project '${{ parameters.projectName }}' version '${{ parameters.umbracoVersion }}'." + exit 1 + fi + echo "Created project '${{ parameters.projectName }}' with version '${{ parameters.umbracoVersion }}' (ID: $project_id)." + fi + displayName: Ensure main project exists in Dependency Track + +- ${{ each project in parameters.projects }}: + - job: + displayName: Upload ${{ project.name }} BOM + dependsOn: Create_DT_Project + steps: + - checkout: none + + - download: current + artifact: ${{ project.artifact }} + displayName: Download ${{ project.artifact }} artifact + + - script: | + curl --no-progress-meter --fail-with-body \ + -X POST "$(DT_API_URL)/v1/bom" \ + -H "X-Api-Key: $(DT_API_KEY)" \ + -H "Content-Type: multipart/form-data" \ + -F "autoCreate=true" \ + -F "projectName=${{ parameters.projectName }}-${{ project.name }}" \ + -F "projectVersion=${{ parameters.umbracoVersion }}" \ + -F "parentName=${{ parameters.projectName }}" \ + -F "parentVersion=${{ parameters.umbracoVersion }}" \ + -F "bom=@$(Pipeline.Workspace)/${{ project.artifact }}/${{ project.bomFilePath }}" + displayName: Upload ${{ project.name }} BOM to Dependency Track diff --git a/build/templates/e2e-install.yml b/build/templates/e2e-install.yml new file mode 100644 index 0000000000..3c2909f3fb --- /dev/null +++ b/build/templates/e2e-install.yml @@ -0,0 +1,49 @@ +parameters: + - name: nodeVersion + type: string + default: '' + + - name: npm_config_cache + type: string + default: '' + + - name: PlaywrightUserEmail + type: string + default: '' + + - name: PlaywrightPassword + type: string + default: '' + + - name: ASPNETCORE_URLS + type: string + default: '' + +steps: + - task: NodeTool@0 + displayName: Use Node.js $(nodeVersion) + inputs: + versionSpec: $(nodeVersion) + + - pwsh: | + "UMBRACO_USER_LOGIN=${{ parameters.PlaywrightUserEmail }} + UMBRACO_USER_PASSWORD=${{ parameters.PlaywrightPassword }} + URL=${{ parameters.ASPNETCORE_URLS }} + STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json + CONSOLE_ERRORS_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/console-errors.json" | Out-File .env + displayName: Generate .env + workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest + + # Cache and restore NPM packages + - task: Cache@2 + displayName: Cache NPM packages + inputs: + key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json' + restoreKeys: | + npm_e2e | "$(Agent.OS)" + npm_e2e + path: ${{ parameters.npm_config_cache }} + + - script: npm ci --no-fund --no-audit --prefer-offline + workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest + displayName: Restore NPM packages