tasks/analysis.tasks.ps1

$covenantVersion = "0.12.0"
$CovenantIncludeSpdxReport = $true
$CovenantIncludeCycloneDxReport = $true
$CovenantMetadata = @{
    git_repo = $(if (Get-Command "gh" -ErrorAction SilentlyContinue) { & gh repo view --json nameWithOwner | ConvertFrom-Json | Select-Object -expandproperty nameWithOwner })
    git_branch = $(if (Get-Command "git" -ErrorAction SilentlyContinue) {git branch --show-current })
    git_sha = $(if (Get-Command "git" -ErrorAction SilentlyContinue) { & git rev-parse HEAD })
}

# Defaults for publishing-related variables
$PublishCovenantOutputToStorage = $false
$AnalysisOutputStorageAccountName = ""
$AnalysisOutputStorageAccountResourceGroup = ""
$AnalysisOutputStorageAccountSubscription = ""
$AnalysisOutputContainerName = ""
$AnalysisOutputBlobPath = ""

task InstallCovenantTool {
    Install-DotNetTool -Name covenant -Version $covenantVersion
}

# Synopsis: Setup SBOM metadata for Covenant command-line
task PrepareCovenantMetadata {

    $script:covenantMetadataArgs = @()
    foreach ($key in $CovenantMetadata.Keys) {
        # NOTE: No space after the '-m' switch otherwise the metadata key has a leading space in the report
        $script:covenantMetadataArgs += "-m$key=$($CovenantMetadata[$key])"
    }
}

task RunCovenantTool -If { $SolutionToBuild } Version,
                                              InstallCovenantTool,
                                              PrepareCovenantMetadata,{

    $baseOutputName = [IO.Path]::GetFileNameWithoutExtension($SolutionToBuild)
    # Ensure we have a fully-qualified path, as this will be needed when uploading on build server
    $script:covenantJsonOutputFile = Join-Path $here ("/{0}.sbom.json" -f $baseOutputName)
    $script:covenantSpdxOutputFile = Join-Path $here ("/{0}.sbom.spdx.json" -f $baseOutputName)
    $script:covenantCycloneDxOutputFile = Join-Path $here ("/{0}.sbom.cyclonedx.xml" -f $baseOutputName)
    $script:covenantHtmlReportFile = Join-Path $here ("/{0}.sbom.html" -f $baseOutputName)
    Write-Verbose "covenantHtmlReportFile: $covenantHtmlReportFile"

    # Generate SBOM
    exec {
        & dotnet-covenant `
                    generate `
                    $SolutionToBuild `
                    -v $script:GitVersion.SemVer `
                    --output $covenantJsonOutputFile `
                    $covenantMetadataArgs
    }

    # Generate HTML report
    exec {
        & dotnet-covenant `
                    report `
                    $covenantJsonOutputFile `
                    --output $covenantHtmlReportFile

    }
}

# Synopsis: Generate SPDX-formatted report
task GenerateCovenantSpdxReport -If { $CovenantIncludeSpdxReport } {
    exec {
        & dotnet-covenant `
                    convert `
                    spdx `
                    $covenantJsonOutputFile `
                    --output $covenantSpdxOutputFile

    }
    Write-Verbose "covenantSpdxOutputFile: $covenantSpdxOutputFile"
}

# Synopsis: Generate CycloneDX-formatted report
task GenerateCovenantCycloneDxReport -If { $CovenantIncludeCycloneDxReport } {
    exec {
        & dotnet-covenant `
                    convert `
                    cyclonedx `
                    $covenantJsonOutputFile `
                    --output $covenantCycloneDxOutputFile

    }
    Write-Verbose "covenantCycloneDxOutputFile: $covenantCycloneDxOutputFile"
}

task PublishCovenantBuildArtefacts -If { $IsAzureDevops } {
    Write-Host "##vso[task.setvariable variable=SbomHtmlReportPath;isoutput=true]$covenantHtmlReportFile"
    Write-Host "##vso[artifact.upload artifactname=SBOM]$covenantHtmlReportFile"
    Write-Host "##vso[artifact.upload artifactname=SBOM]$covenantJsonOutputFile"

    if ($CovenantIncludeSpdxReport) {
        Write-Host "##vso[artifact.upload artifactname=SBOM]$covenantSpdxOutputFile"
    }

    if ($CovenantIncludeCycloneDxReport) {
        Write-Host "##vso[artifact.upload artifactname=SBOM]$covenantCycloneDxOutputFile"
    }
}

task PublishCovenantOutputToStorage -If { $SolutionToBuild -and $PublishCovenantOutputToStorage } {
    if ( (Test-Path $covenantJsonOutputFile) -and `
            $AnalysisOutputStorageAccountName -and `
            $AnalysisOutputStorageAccountResourceGroup -and `
            $AnalysisOutputStorageAccountSubscription -and `
            $AnalysisOutputContainerName -and `
            $AnalysisOutputBlobPath) {
    
        $covenantJsonOutputFilename = (Split-Path -Leaf $covenantJsonOutputFile)
        $filename = "{0}-{1}.json" -f [IO.Path]::GetFileNameWithoutExtension($covenantJsonOutputFilename),
                                     ([DateTime]::Now).ToString('yyyyMMddHHmmssfff')

        Write-Information @"
Publishing storage account:
    Source File: $covenantJsonOutputFile
    Account: $AnalysisOutputStorageAccountName
    ResourceGroup: $AnalysisOutputStorageAccountResourceGroup
    Subscription: $AnalysisOutputStorageAccountSubscription
    Blob Path: "$AnalysisOutputContainerName/$AnalysisOutputBlobPath/$filename"
"@


        Set-AzContext -Subscription $AnalysisOutputStorageAccountSubscription
        $storage = Get-AzStorageAccount `
                        -Name $AnalysisOutputStorageAccountName `
                        -ResourceGroupName $AnalysisOutputStorageAccountResourceGroup

        Set-AzStorageBlobContent `
                -File $covenantJsonOutputFile `
                -Container $AnalysisOutputContainerName `
                -Blob "$AnalysisOutputBlobPath/$filename" `
                -Context $storage.Context | Out-Null

        Write-Information "Covenant JSON output published to storage account"
    }
    else {
        Write-Information "Publishing of Covenant output skipped, due to absent configuration"
    }
}

task RunCovenant RunCovenantTool,
                 GenerateCovenantSpdxReport,
                 GenerateCovenantCycloneDxReport,
                 PublishCovenantBuildArtefacts,
                 PublishCovenantOutputToStorage