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" # Allow cross-subscription ACR access $AcrSubscription = "" # 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 | % { # Support deferred evaluation of container build arguments $argValue = $buildInfo.Arguments[$_] -is [scriptblock] ? $buildInfo.Arguments[$_].Invoke() : $buildInfo.Arguments[$_] # Construct the command-line arguments required by 'docker build' and 'az acr build' "--build-arg $_=$argValue" } } # Setup common build parameters $buildParameters = @( "--file $($buildInfo.Dockerfile)" ) if ($containerBuildArgs) { $buildParameters += $containerBuildArgs -join " " } if ($buildInfo.Target) { $buildParameters += "--target $($buildInfo.Target)" } 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 # Since images are published automatically when built via ACR Tasks, we need to tag the image with a '--pre' # suffix to signify that we don't yet know whether this build will be good. (i.e.when running in CI the tests # will be running in parallel so they could fail after the image has been published) $buildParameters += @( "-t $acrPublishTag--pre" "--registry $ContainerRegistryFqdn" ) # Support cross-subscription ACR access if ($AcrSubscription) { $buildParameters += "--subscription $AcrSubscription" } $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" } 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" } if (!$UseAcrTasks) { Write-Host "Logging-in to ACR $ContainerRegistryFqdn $($AcrSubscription ? "(Subscription=$AcrSubscription)" : '') " exec { az acr login -n $ContainerRegistryFqdn $($AcrSubscription ? "--subscription",$AcrSubscription : $null) } } foreach ($buildInfo in $ContainersToBuild) { $buildTag = "{0}:{1}" -f $buildInfo.ImageName, $containerBuildTag $publishTag = $ContainerRegistryPublishPrefix ? ` "$ContainerRegistryFqdn/$ContainerRegistryPublishPrefix/$buildTag" : ` "$ContainerRegistryFqdn/$buildTag" Write-Host "Publishing Container: $publishTag" if ($UseAcrTasks) { # Use 'acr import' command to re-tag the previously built image without the '--pre' suffix to reflect its final publication $sourceTag = "$publishTag--pre" $targetTag = $publishTag.Replace("$ContainerRegistryFqdn/", "") Write-Host "Promoting 'pre' container image:`n $sourceTag -> $targetTag" # Support cross-subscription ACR access ('--subscription' is not supported by 'az acr import' so we need to switch subscriptions temporarily) $currentSubscription = $null if ($AcrSubscription) { $currentSubscription = exec { az account show --query id --output tsv } Write-Host "Temporarily switching to ACR subscription: $currentSubscription -> $AcrSubscription" exec { az account set --subscription $AcrSubscription } | Out-Null } try { exec { & az acr import --name $ContainerRegistryFqdn --source $sourceTag --image $targetTag --force } exec { & az acr repository untag -n $ContainerRegistryFqdn --image "$targetTag--pre" } } finally { if ($currentSubscription) { Write-Host "Revert subscription: $AcrSubscription -> $currentSubscription" exec { az account set --subscription $currentSubscription } | Out-Null } } } else { # Tag and push the image 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 |