tasks/container.tasks.ps1

# ContainersToBuild must be an array of hashtables of the following structure:
# @(
# @{
# Dockerfile = "<path-to-dockerfile>"
# ImageName = "<container-image-name-without-tag>"
# ContextDir = "<path-to-docker-build-context-dir>"
# Arguments = @{ Name = "Value" }
# }
# )
$ContainersToBuild = @()
$UseAcrTasks = $false
$SkipPublishContainerImages = $false

# Allows any containers to be built with a custom version tag, rather than the default SemVer provided by GitVersion
$ContainerImageVersionOverride = $null
$ContainerRegistryPublishPrefix = $null
$ContainerImageTagArtefactPath = Join-Path $PWD "image-tag"

# Default settings for Docker Registry publishing
$DockerRegistryFqdn = "docker.io"
$DockerRegistryUsername = $null


# Synopsis: Build Container Images
task BuildContainerImages -If {!$SkipContainerImages -and $ContainersToBuild} GenerateContainerBuildTag,{

    # If building images locally, check whether a Docker daemon is available so we can fail fast with a useful error message
    try {
        if (!$UseAcrTasks) {
            exec { docker ps }
        }
    }
    catch {
        throw "Unable to build container images - Docker is not installed or not running"
    }

    foreach ($buildInfo in $ContainersToBuild) {
        $contextDir = $buildInfo.ContainsKey("ContextDir") ? $buildInfo.ContextDir : (Split-Path -Parent $buildInfo.Dockerfile)
        $buildTag = "{0}:{1}" -f $buildInfo.ImageName.ToLower(), $containerBuildTag

        $containerBuildArgs = $null
        if ($buildInfo.Arguments) {
            $containerBuildArgs = $buildInfo.Arguments.Keys | % {
                # Construct the command-line arguments required by 'docker build' and 'az acr build'
                "--build-arg $_=$($buildInfo.Arguments[$_])"
            }            
        }

        # Setup common build parameters
        $buildParameters = @(
            "--file $($buildInfo.Dockerfile)"
        )
        if ($containerBuildArgs) {
            $buildParameters += $containerBuildArgs -join " "
        }

        if ($UseAcrTasks) {
            if (!(Test-AzCliConnection)) {
                throw "You must be logged in to Azure CLI to build using ACR Tasks"
            }

            Write-Build White "Building & publishing image with ACR Tasks"
            $acrPublishTag = $ContainerRegistryPublishPrefix ? `
                                "$ContainerRegistryPublishPrefix/$buildTag" : `
                                $buildTag

            # Set command-line for building via ACR Tasks
            $buildParameters += @(
                "-t $acrPublishTag"
                "--registry $ContainerRegistryFqdn"
            )

            $buildCmd = "az acr build"
        }
        else {
            # Set command-line for building with docker
            $buildParameters += "-t $buildTag"
            $buildCmd = "docker build"               
        }

        # Add the final positional command-line argument
        $buildParameters += $contextDir

        # Form the command-line
        $buildCmdline = "$buildCmd $($buildParameters -join " ")"
        Write-Verbose "buildCmdline: $buildCmdline"
        exec {
            Invoke-Expression $buildCmdline
        } 
    }
}

# Synopsis: Set the container image version
task GenerateContainerBuildTag Version,{

    if ($ContainerImageVersionOverride) {
        Write-Host "Overriding default container image version tag: $ContainerImageVersionOverride"
        $script:containerBuildTag = $ContainerImageVersionOverride
    }
    else {
        $script:containerBuildTag = ($script:GitVersion).SemVer
    }
}

# Synopsis: Publish Container Images to a Docker Registry
task PublishContainerImagesToDocker -If { $ContainerRegistryType -eq "docker" } GenerateContainerBuildTag,{

    if (!$ContainerRegistryPublishPrefix -and !$DockerRegistryUsername) {
        throw "Either 'ContainerRegistryPublishPrefix' or 'DockerRegistryUsername' must be defined when publishing to a Docker Registry"
    }

    foreach ($buildInfo in $ContainersToBuild) {
        $buildTag = "{0}:{1}" -f $buildInfo.ImageName.ToLower(), $containerBuildTag
        $publishTag = $ContainerRegistryPublishPrefix ? `
                            "$DockerRegistryFqdn/$ContainerRegistryPublishPrefix/$buildTag" : `
                            "$DockerRegistryFqdn/$DockerRegistryUsername/$buildTag"
        Write-Host "Publishing Container: $publishTag"
        exec {
            docker tag $buildTag $publishTag
            docker push $publishTag
        }
    }
}

# Synopsis: Publish Container Images to GHCR
task PublishContainerImagesToGhcr -If { $ContainerRegistryType -eq "ghcr" } EnsureGitHubCli,GenerateContainerBuildTag,{

    foreach ($buildInfo in $ContainersToBuild) {
        $buildTag = "{0}:{1}" -f $buildInfo.ImageName, $containerBuildTag
        $publishTag = $ContainerRegistryPublishPrefix ? `
                            "docker.pkg.github.com/$ContainerRegistryPublishPrefix/$buildTag" : `
                            "docker.pkg.github.com/$(gh repo view --json nameWithOwner | ConvertFrom-Json | Select-Object -ExpandProperty nameWithOwner)/$buildTag"
        Write-Host "Publishing Container: $publishTag"
        exec {
            docker tag $buildTag $publishTag
            docker push $publishTag
        }
    }
}

# Synopsis: Publish Container Images to Azure Container Registry
task PublishContainerImagesToAcr -If { $ContainerRegistryType -eq "acr" -and !$UseAcrTasks} GenerateContainerBuildTag,{

    if (!$ContainerRegistryFqdn) {
        throw "Missing value for 'ContainerRegistryFqdn' - this is required when publishing to Azure Container Registry"
    }

    if (!(Test-AzCliConnection)) {
        throw "You must be logged in to Azure CLI to publish to ACR"
    }

    Write-Host "Logging-in to ACR"
    exec { az acr login -n $ContainerRegistryFqdn }

    foreach ($buildInfo in $ContainersToBuild) {
        $buildTag = "{0}:{1}" -f $buildInfo.ImageName, $containerBuildTag
        $publishTag = $ContainerRegistryPublishPrefix ? `
                            "$ContainerRegistryFqdn/$ContainerRegistryPublishPrefix/$buildTag" : `
                            "$ContainerRegistryFqdn/$buildTag"
        Write-Host "Publishing Container: $publishTag"
        exec { & docker tag $buildTag $publishTag }
        exec { docker push $publishTag }
    }
}

# Synopsis: Outputs the tag used for the built container images (e.g. local file, Build Server output variable)
task OutputContainerImageTagArtefact GenerateContainerBuildTag,{

    if ($containerBuildTag) {

        Write-Host "Creating build artefact to record the container image tag"
        Set-Content -Path $ContainerImageTagArtefactPath -Value $containerBuildTag

        Write-Host "ContainerImageTagArtefactPath: $ContainerImageTagArtefactPath"
        Write-Host "ContainerImageTag: $containerBuildTag"
    
        # Ensure we give a full path to the artefact/file
        $artefactFilePath = [IO.Path]::GetFullPath($ContainerImageTagArtefactPath)
        Write-Host "artefactFilePath: $artefactFilePath"

        if ($IsAzureDevops) {
            Write-Host "Sending container image tag details to Azure Pipelines..."
            Write-Host "##vso[task.uploadsummary]$artefactFilePath"
            Write-Host "##vso[task.setvariable variable=ContainerImageTagArtefactPath]$artefactFilePath"
            Write-Host "##vso[task.setvariable variable=ContainerImageTag]$containerBuildTag"
        }
        elseif ($IsGitHubActions) {
            Write-Host "Sending container image tag details to GitHub Actions..."
            "ContainerImageTagArtefactPath=$artefactFilePath" | Out-File -Encoding utf8 -Append $env:GITHUB_OUTPUT
            "ContainerImageTag=$containerBuildTag" | Out-File -Encoding utf8 -Append $env:GITHUB_OUTPUT
        }
    }
    else {
        Write-Warning "No 'image-tag' artefact was created due to the 'containerBuildTag' not being available."
    }
}

task PublishContainerImages -If { !$SkipPublishContainerImages } PublishContainerImagesToAcr,
                            PublishContainerImagesToGhcr,
                            PublishContainerImagesToDocker,
                            OutputContainerImageTagArtefact