Skip to content

Commit

Permalink
Include client payload data (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
mthalman authored Jun 29, 2024
1 parent 549e3ef commit c83d920
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 20 deletions.
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ on:

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: mthalman/docker-bump-action@v0
with:
Expand Down Expand Up @@ -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
Expand Down
48 changes: 38 additions & 10 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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`" `
Expand All @@ -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"
}
Expand All @@ -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"
1 change: 1 addition & 0 deletions container/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
36 changes: 31 additions & 5 deletions container/check-image.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion container/entrypoint.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
45 changes: 43 additions & 2 deletions container/tests/check-image.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,24 @@ Describe 'Get result' {
$result | ConvertTo-Json
} -ParameterFilter { $Command -eq "dredge image compare layers --output json $baseImage $targetImage --os linux --arch $architecture" } `
-ModuleName common
& $targetScript -TargetImage $targetImage -BaseImage $baseImage -Architecture $architecture | Should -Be "false"

Mock Invoke-Expression {
"base-digest"
} -ParameterFilter { $Command -eq "dredge manifest resolve $baseImage --os linux --arch $architecture" } `
-ModuleName common

Mock Invoke-Expression {
"target-digest"
} -ParameterFilter { $Command -eq "dredge manifest resolve $targetImage --os linux --arch $architecture" } `
-ModuleName common
$result = & $targetScript -TargetImage $targetImage -BaseImage $baseImage -Architecture $architecture

$expected = @{
sendDispatch = "false"
updates = @()
} | ConvertTo-Json

$result | Should -Be $expected
}

It 'Given a target image that is not up-to-date with the base image, it returns true' {
Expand All @@ -35,8 +52,32 @@ Describe 'Get result' {
$result | ConvertTo-Json
} -ParameterFilter { $Command -eq "dredge image compare layers --output json $baseImage $targetImage --os linux --arch $architecture" } `
-ModuleName common

Mock Invoke-Expression {
"base-digest"
} -ParameterFilter { $Command -eq "dredge manifest resolve $baseImage --os linux --arch $architecture" } `
-ModuleName common

Mock Invoke-Expression {
"target-digest"
} -ParameterFilter { $Command -eq "dredge manifest resolve $targetImage --os linux --arch $architecture" } `
-ModuleName common
$result = & $targetScript -TargetImage $targetImage -BaseImage $baseImage -Architecture $architecture

$expected = @{
sendDispatch = "true"
updates = @(
@{
targetImageName = $targetImage
targetImageDigest = "target-digest"
dockerfile = ""
baseImageName = $baseImage
baseImageDigest = "base-digest"
}
)
} | ConvertTo-Json

& $targetScript -TargetImage $targetImage -BaseImage $baseImage -Architecture $architecture | Should -Be "true"
$result | Should -Be $expected
}

It 'Given a failed dredge command, it throws an error' {
Expand Down

0 comments on commit c83d920

Please sign in to comment.