tasks/analysis.tasks.ps1

$covenantVersion = "0.12.0"
$CovenantIncludeSpdxReport = $true
$CovenantIncludeCycloneDxReport = $true
$CovenantMetadata = @{
    git_repo = $(if (Get-Command "gh" -ErrorAction SilentlyContinue) {
        try {
            $ghRepo = & gh repo view --json nameWithOwner
            $ghRepo | ConvertFrom-Json | Select-Object -expandproperty nameWithOwner
        }
        catch {
            ""
        }
    })
    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 = ""
$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 { $SolutionToBuild -and $CovenantIncludeSpdxReport } RunCovenantTool,{
    exec {
        & dotnet-covenant `
                    convert `
                    spdx `
                    $covenantJsonOutputFile `
                    --output $covenantSpdxOutputFile

    }
    Write-Verbose "covenantSpdxOutputFile: $covenantSpdxOutputFile"
}

# Synopsis: Generate CycloneDX-formatted report
task GenerateCovenantCycloneDxReport -If { $SolutionToBuild -and $CovenantIncludeCycloneDxReport } RunCovenantTool,{
    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 `
            $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
    Blob Path: "$AnalysisOutputContainerName/$AnalysisOutputBlobPath/$filename"
"@


        $uri = "https://{0}.blob.core.windows.net/{1}/{2}/{3}" -f $AnalysisOutputStorageAccountName,
                        $AnalysisOutputContainerName,
                        $AnalysisOutputBlobPath,
                        $filename

        $authUri = & az storage blob generate-sas `
                                --auth-mode login `
                                --as-user `
                                --https-only `
                                --account-name $AnalysisOutputStorageAccountName `
                                --blob-url $uri `
                                --permissions c `
                                --start (Get-Date).ToUniversalTime().ToString("yyyy-M-d'T'H:m'Z'") `
                                --expiry (Get-Date).AddMinutes(10).ToUniversalTime().ToString("yyyy-M-d'T'H:m'Z'") `
                                --full-uri `
                                -o tsv

        $headers = @{
            "x-ms-date" = [System.DateTime]::UtcNow.ToString("R")
            "x-ms-blob-type" = "BlockBlob"
        }
        Invoke-RestMethod -Headers $headers `
                  -Uri $authUri `
                  -Method PUT `
                  -InFile $covenantJsonOutputFile `
                  -Verbose:$false | 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