From c5224a2122064b6b7b633657ec5139a77a5b2c7c Mon Sep 17 00:00:00 2001 From: Matt Thalman Date: Thu, 2 May 2024 18:52:39 -0500 Subject: [PATCH] Include client payload data --- README.md | 43 +++++++++++++++++++++++++++++++++-- action.yml | 48 +++++++++++++++++++++++++++++++-------- container/Dockerfile | 1 + container/check-image.ps1 | 36 +++++++++++++++++++++++++---- container/entrypoint.ps1 | 2 +- 5 files changed, 112 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a4beac5..d071a6e 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,7 @@ on: jobs: build: - runs-on: ubuntu-latest - steps: - uses: mthalman/docker-bump-action@v0 with: @@ -83,6 +81,47 @@ The algorithm for deriving the image names is described below: 1. Starting from the last stage, walk the stage hierarchy until the root stage is found. 1. The image name of the root stage is considered the base image name. +## Dispatch Payload + +When the repository dispatch occurs, a payload is sent along with it. This payload includes metadata describing the state of the image that resulted in an update being needed. This payload can be retrieved in the workflow that responds to the dispatch via the `client-payload` event. Consuming this payload is completely optional and only necessary if your workflow requires more context regarding the dispatch. + +```yaml +name: Build Image +on: + repository_dispatch: + types: [base-image-update] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Show Rebuild Info + if: ${{ github.event.client_payload }} + run: | + target="${{ github.event.client_payload.updates[0].targetImageName }}" + base="${{ github.event.client_payload.updates[0].baseImageName }}" + echo "Rebuilding $target to be in sync with $base" +``` + +### Payload Schema + +Each object in the `updates` array represents an image that the action has determined requires an update. +Currently the action only supports targeting single images and so this array will always have a single element. +See #3 for support for multiple images. + +```json +{ + "updates": [ + { + "targetImageName": "Name of the target image provided as input to the action", + "targetImageDigest": "Current digest of the target image", + "dockerfile": "Relative path of the Dockerfile", + "baseImageName": "Name of the base image that was either provided as input to the action or derived via other state", + "baseImageDigest": "Current digest of the base image" + } + ] +} +``` + ## Examples ### Explicitly set base stage name diff --git a/action.yml b/action.yml index e197118..061b363 100644 --- a/action.yml +++ b/action.yml @@ -28,11 +28,16 @@ inputs: outputs: dispatch-sent: description: "A value indicating whether a repository dispatch was sent" - value: ${{ steps.report-status.outputs.dispatch-sent }} + value: ${{ steps.check-image.outputs.SEND_DISPATCH }} + dispatch-payload: + description: "The payload data that was sent with the dispatch" + value: ${{ steps.check-image.outputs.OUTPUT_DISPATCH_PAYLOAD }} + runs: using: "composite" steps: - name: Check Image + id: check-image shell: pwsh run: | $ErrorActionPreference = 'Stop' @@ -46,16 +51,20 @@ runs: throw "'dockerfile' input not provided. This is required when 'base-image-name' is not provided." } - $dockerBumpCheckerVersion = "0.2.0" + $dockerBumpCheckerVersion = "0.3.0" $containerName = "docker-bump-checker" $containerSrcPath = "/src" + if ($dockerfile.StartsWith(${env:GITHUB_WORKSPACE})) { + $dockerfile = $dockerfile.Substring(${env:GITHUB_WORKSPACE}.Length).TrimStart('/') + } + # The repo directory will be volume-mounted into the container so the Dockerfile path needs to be modified accordingly - $dockerfile = "$containerSrcPath/$dockerfile" $result = docker run ` --name $containerName ` -v ${env:GITHUB_WORKSPACE}:$containerSrcPath ` + -w $containerSrcPath ` ghcr.io/mthalman/docker-bump-checker:$dockerBumpCheckerVersion ` -BaseImage `"$baseImage`" ` -TargetImage `"$targetImage`" ` @@ -64,7 +73,7 @@ runs: -Architecture `"$arch`" $dockerRunExitCode = $LASTEXITCODE - docker cp ${containerName}:/home/app/log.txt log.txt + docker cp ${containerName}:/home/app/log.txt log.txt > $null 2>&1 if ($LASTEXITCODE -ne 0) { throw "Unable to retrieve log" } @@ -79,23 +88,42 @@ runs: if ($LASTEXITCODE -ne 0) { throw "command failed" } + + $result = $result | ConvertFrom-Json + + # Need to track two different payloads. This is only to account for the scenario where no dispatch is sent. + # In that scenario, we don't send the dispatch but the repository-dispatch action will still validate its + # client-payload input which needs to be valid JSON. So that is defaulted here. The other payload is the + # output of this composite action. That needs to be empty when no dispatch is sent. In the scenario where + # a dispatch is sent, both these payload variables get set to the same value. + $actionPayload = "{}" + $outputPayload = "" + + if ($result.sendDispatch -eq "true") { + $payloadObj = @{ + updates = $result.updates + } + + $actionPayload = ,$payloadObj | ConvertTo-Json -Compress + $outputPayload = $actionPayload + } - echo "SEND_DISPATCH=$result" >> "$env:GITHUB_ENV" + echo "SEND_DISPATCH=$($result.sendDispatch)" >> $env:GITHUB_OUTPUT + echo "ACTION_DISPATCH_PAYLOAD=$actionPayload" >> $env:GITHUB_OUTPUT + echo "OUTPUT_DISPATCH_PAYLOAD=$outputPayload" >> $env:GITHUB_OUTPUT - name: Repository Dispatch - if: env.SEND_DISPATCH == 'true' + if: ${{ steps.check-image.outputs.SEND_DISPATCH }} == 'true' uses: peter-evans/repository-dispatch@v3 with: token: ${{ inputs.token }} repository: ${{ inputs.repository }} event-type: ${{ inputs.event-type }} + client-payload: ${{ steps.check-image.outputs.ACTION_DISPATCH_PAYLOAD }} - name: Report Status - id: report-status shell: pwsh run: | - if ($env:SEND_DISPATCH -eq "true") { + if ("${{ steps.check-image.outputs.SEND_DISPATCH }}" -eq "true") { echo "A repository dispatch was sent to '${{ inputs.repository }}' with event type '${{ inputs.event-type }}'." } else { echo "No repository dispatch was sent." } - - echo "dispatch-sent=$env:TRIGGER_WORKFLOW" >> "$env:GITHUB_OUTPUT" diff --git a/container/Dockerfile b/container/Dockerfile index 063b318..dd94bad 100644 --- a/container/Dockerfile +++ b/container/Dockerfile @@ -15,6 +15,7 @@ COPY --from=installer /usr/share/powershell /usr/share/powershell COPY --from=installer /root/.dotnet/tools /home/app/.dotnet/tools COPY --from=installer ["/symlinks", "/usr/bin"] COPY *.ps1 /scripts/ +COPY *.psm1 /scripts/ # Returns 'true' in the output if the image is out-of-date in relation to its base image; otherwise, 'false'. ENTRYPOINT ["pwsh", "-c", "/scripts/entrypoint.ps1"] diff --git a/container/check-image.ps1 b/container/check-image.ps1 index ed35fea..da9f046 100644 --- a/container/check-image.ps1 +++ b/container/check-image.ps1 @@ -7,23 +7,49 @@ param( [string]$BaseImage, [Parameter(Mandatory = $True)] - [string]$Architecture + [string]$Architecture, + + [string]$DockerfilePath ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' Set-StrictMode -Version 2.0 -Import-Module $PSScriptRoot/common.psm1 +function GetDigest($imageName) { + $digestCmd = "dredge manifest resolve $imageName --os linux --arch $Architecture" + $digest = $(InvokeTool $digestCmd "dredge manifest resolve failed") + return $digest +} -$cmd = "dredge image compare layers --output json $BaseImage $TargetImage --os linux --arch $Architecture" -$layerComparisonStr = $(InvokeTool $cmd "dredge image compare failed") +Import-Module $PSScriptRoot/common.psm1 +$compareCmd = "dredge image compare layers --output json $BaseImage $TargetImage --os linux --arch $Architecture" +$layerComparisonStr = $(InvokeTool $compareCmd "dredge image compare failed") $layerComparison = $layerComparisonStr | ConvertFrom-Json $imageUpToDate = [bool]$($layerComparison.summary.targetIncludesAllBaseLayers) $sendDispatch = ([string](-not $imageUpToDate)).ToLower() +$targetDigest = $(GetDigest $TargetImage) +$baseDigest = $(GetDigest $BaseImage) + LogMessage "Send dispatch: $sendDispatch" -return $sendDispatch +$updates = @() +if (-not $imageUpToDate) { + $updates += @{ + targetImageName = $TargetImage + targetImageDigest = $targetDigest + dockerfile = $DockerfilePath + baseImageName = $BaseImage + baseImageDigest = $baseDigest + } +} + +$result = @{ + sendDispatch = $sendDispatch + updates = $updates +} | ConvertTo-Json + +return $result diff --git a/container/entrypoint.ps1 b/container/entrypoint.ps1 index 258b482..cd3668e 100644 --- a/container/entrypoint.ps1 +++ b/container/entrypoint.ps1 @@ -21,6 +21,6 @@ if (-not $BaseImage) { $BaseImage = $(& $PSScriptRoot/get-base-image.ps1 -DockerfilePath $DockerfilePath -BaseStageName $BaseStageName) } -$result = $(& $PSScriptRoot/check-image.ps1 -TargetImage $targetImage -BaseImage $BaseImage -Architecture $Architecture) +$result = $(& $PSScriptRoot/check-image.ps1 -TargetImage $targetImage -BaseImage $BaseImage -Architecture $Architecture -DockerfilePath $DockerfilePath) return $result