chore: introduce docker release pipeline (#4618)

* chore: introduce docker release pipeline

The whole flow is reimplemented using native arm64/amd64 runners.

Signed-off-by: Roman Gershman <roman@dragonflydb.io>

* Update .github/workflows/docker-release2.yml

Co-authored-by: Kostas Kyrimis  <kostas@dragonflydb.io>
Signed-off-by: Roman Gershman <romange@gmail.com>

* chore: comments

---------

Signed-off-by: Roman Gershman <roman@dragonflydb.io>
Signed-off-by: Roman Gershman <romange@gmail.com>
Co-authored-by: Kostas Kyrimis <kostas@dragonflydb.io>
This commit is contained in:
Roman Gershman
2025-02-17 12:24:24 +02:00
committed by GitHub
parent fe0da9c013
commit 52d88c2372
5 changed files with 186 additions and 330 deletions

25
.github/actions/test-docker/action.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Test Docker Image
inputs:
image_id:
required: true
type: string
name:
required: true
type: string
runs:
using: "composite"
steps:
- name: Test Image
shell: bash
run: |
echo "Testing ${{ inputs.name }} image"
docker pull ${{inputs.image_id}}
docker image inspect ${{inputs.image_id}}
# docker run with port-forwarding
docker run --name test -d -p 6379:6379 ${{inputs.image_id}}
until [ "`docker inspect -f {{.State.Health.Status}} test`"=="healthy" ]; do
sleep 0.1;
done;

View File

@ -1,124 +0,0 @@
name: docker release
on:
workflow_dispatch:
inputs:
TAG_NAME:
description: 'Tag name that the major tag will point to'
required: true
PRERELEASE:
description: 'Whether this is a prerelease'
type: boolean
required: true
release:
types: [published]
permissions:
packages: write
contents: write
id-token: write
env:
TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }}
IS_PRERELEASE: ${{ github.event.release.prerelease || github.event.inputs.PRERELEASE }}
jobs:
# This workaround is needed, as it's not possible to reference env.FOOBAR directly at this stage
# - for ex. https://github.com/actions/runner/issues/1189
release-container-prereq:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.release-param.outputs.tag }}
tag_latest: ${{ steps.release-param.outputs.tag_latest }}
steps:
- id: release-param
run: |
# If env.IS_PRERELEASE is true, set tag to alpha and do not enable tag_latest
# If env.IS_PRERELEASE is not true (aka false), don't set an extra tag and enable tag_latest
echo "tag=${{ env.IS_PRERELEASE == 'true' && 'alpha' }}" >> $GITHUB_OUTPUT
echo "tag_latest=${{ env.IS_PRERELEASE == 'true' && 'false' || 'true' }}" >> $GITHUB_OUTPUT
release-container:
needs: release-container-prereq
uses: ./.github/workflows/reusable-container-workflow.yaml
with:
build_type: prod
tag: ${{ needs.release-container-prereq.outputs.tag }}
tag_latest: ${{ needs.release-container-prereq.outputs.tag_latest == 'true' }}
image: ghcr.io/${{ github.repository }}
registry: ghcr.io
registry_username: ${{ github.repository_owner }}
fetch_release: true
release_version: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }}
secrets:
registry_password: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release:
needs: [release-container-prereq, release-container]
runs-on: ubuntu-latest
steps:
- name: print_env
run: env
- name: checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install helm
uses: azure/setup-helm@v4
- name: Setup Go
uses: actions/setup-go@v5
- name: Configure Git
if: env.IS_PRERELEASE != 'true'
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Update helm chart
if: env.IS_PRERELEASE != 'true'
run: |
git checkout main
sed -Ei \
-e 's/^(version\:) .*/\1 '${{ env.TAG_NAME }}'/g' \
-e 's/^(appVersion\:) .*/\1 "'${{ env.TAG_NAME }}'"/g' \
contrib/charts/dragonfly/Chart.yaml
go test ./contrib/charts/dragonfly/... -update
git commit \
-m 'chore(helm-chart): update to ${{ env.TAG_NAME }}' \
contrib/charts/dragonfly/Chart.yaml \
contrib/charts/dragonfly/ci || true
- name: Push Helm chart as OCI to Github
if: env.IS_PRERELEASE != 'true'
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | \
helm registry login -u ${{ github.actor }} --password-stdin ghcr.io
helm package contrib/charts/dragonfly
helm push dragonfly-${{ env.TAG_NAME }}.tgz oci://ghcr.io/${{ github.repository }}/helm
- name: GitHub Push
uses: CasperWA/push-protected@v2
with:
token: ${{ secrets.DRAGONFLY_TOKEN }}
branch: main
- name: Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0c4b27844ba47cb1c7bee539c8eead5284ce9fa9
with:
args: 'DragonflyDB version [${{ env.TAG_NAME }}](https://github.com/dragonflydb/dragonfly/releases/tag/${{ env.TAG_NAME }}) has been released 🎉'
- name: Re-build Docs
if: env.IS_PRERELEASE != 'true'
run: |
curl -s -X POST '${{ secrets.VERCEL_DOCS_WEBHOOK }}'

160
.github/workflows/docker-release2.yml vendored Normal file
View File

@ -0,0 +1,160 @@
name: Docker Release-v2
on:
workflow_dispatch:
inputs:
TAG_NAME:
description: 'Tag name that the major tag will point to'
required: true
PRERELEASE:
description: 'Whether this is a prerelease'
type: boolean
required: true
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }}
IS_PRERELEASE: ${{ github.event.release.prerelease || github.event.inputs.PRERELEASE }}
IMAGE: ghcr.io/dragonflydb/dragonfly
jobs:
build_and_tag:
name: Build and Push ${{matrix.flavor}} ${{ matrix.os.arch }} image
strategy:
matrix:
flavor: [ubuntu]
os:
- image: ubuntu-24.04
arch: amd64
- image: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.os.image }}
permissions:
contents: read
packages: write
id-token: write
steps:
- name: checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- name: Set up Docker Build
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Fetch release asset
uses: dsaltares/fetch-gh-release-asset@1.1.2
with:
version: "tags/${{ env.TAG_NAME }}"
regex: true
file: "dragonfly-.*\\.tar\\.gz"
target: 'releases/'
token: ${{ secrets.GITHUB_TOKEN }}
- name: Extract artifacts
run: |
echo "Event prerelease ${{ github.event.release.prerelease }}"
echo "Input prerelease ${{ github.event.inputs.PRERELEASE }}"
ls -l
ls -l releases
for f in releases/*.tar.gz; do tar xvfz $f -C releases; done
rm releases/*.tar.gz
- name: Docker meta
id: metadata
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE }}
flavor: |
latest=false
prefix=${{ matrix.flavor}}-
suffix=-${{ matrix.os.arch }}
tags: |
type=semver,pattern={{version}},enable=true,value=${{ env.TAG_NAME }}
type=semver,pattern={{raw}},enable=true,value=${{ env.TAG_NAME }}
type=ref,event=pr
labels: |
org.opencontainers.image.vendor=DragonflyDB LTD
org.opencontainers.image.title=Dragonfly Production Image
org.opencontainers.image.description=The fastest in-memory store
- name: Build image
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
provenance: false # Prevent pushing a docker manifest
tags: |
${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
file: tools/packaging/Dockerfile.${{ matrix.flavor }}-prod
cache-from: type=gha,scope=prod-${{ matrix.flavor }}
cache-to: type=gha,scope=prod-${{ matrix.flavor }},mode=max
load: true # Load the build images into the local docker.
- name: Test Image
uses: ./.github/actions/test-docker
timeout-minutes: 1
with:
image_id: ${{ env.IMAGE }}@${{ steps.build.outputs.digest }}
name: ${{ matrix.flavor }}-${{ matrix.os.arch }}
- id: output-sha
run: |
echo "sha_${{ matrix.os.arch }}=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT
outputs:
sha_amd: ${{ steps.output-sha.outputs.sha_amd64 }}
sha_arm: ${{ steps.output-sha.outputs.sha_arm64 }}
merge_manifest:
needs: [build_and_tag]
runs-on: ubuntu-latest
strategy:
matrix:
flavor: [ubuntu]
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Merge and Push
run: |
sha_amd=${{ env.IMAGE }}@${{ needs.build_and_tag.outputs.sha_amd }}
sha_arm=${{ env.IMAGE }}@${{ needs.build_and_tag.outputs.sha_arm }}
echo "shas: $sha_amd $sha_arm"
if [[ "${{ env.IS_PRERELEASE }}" == 'true' ]]; then
# Create and push the manifest like dragonfly:alpha-ubuntu
tag="${{ env.IMAGE }}:alpha-${{ matrix.flavor }}"
docker manifest create ${tag} --amend ${sha_amd} --amend ${sha_arm}
docker manifest push ${tag}
elif [[ "${{matrix.flavor}}" == 'ubuntu' ]]; then
tag="${{ env.IMAGE }}:latest"
# Create and push the manifest like dragonfly:latest
docker manifest create ${tag} --amend ${sha_amd} --amend ${sha_arm}
docker manifest push ${tag}
fi
# Create and push the manifest like dragonfly:v1.26.4
tag=${{ env.IMAGE }}:${{ env.TAG_NAME }}
docker manifest create ${tag} --amend ${sha_amd} --amend ${sha_arm}
docker manifest push ${tag}

View File

@ -1,205 +0,0 @@
name: Reusable Container Build Workflow
on:
workflow_call:
inputs:
# Which suffix to look for with the Dockerfile. Can be dev or prod
build_type:
required: true
type: string
# For example 'alpha', for pre-release or weekly builds
tag:
required: false
type: string
# Is this a final release? Then we set this to true, so the 'latest' tag is updated
tag_latest:
required: false
type: boolean
# The container image dragonflydb/dragonfly
image:
required: true
type: string
# ghcr.io / hub.docker.com / quay.io / you name it
registry:
required: true
type: string
# Username used to login to the registry
registry_username:
required: true
type: string
# Do we have to fetch release assets? Then set this to true.
# Not required for build_type == dev, as they entirely build from source
# But for build_type == prod, as they're based on the release assets
fetch_release:
required: false
type: boolean
# Which version are we fetching? Should be identical to the release version.
# For example v0.12.0
release_version:
required: false
type: string
secrets:
# Password used to login to the registry
registry_password:
required: true
# Github Personal Access Token used to fetch assets from a release
GH_TOKEN:
required: false
jobs:
container-build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- flavor: alpine
dockerfile: tools/packaging/Dockerfile.alpine
tag_main: false
- flavor: ubuntu
dockerfile: tools/packaging/Dockerfile.ubuntu
tag_main: true
steps:
- name: checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: true
- if: inputs.fetch_release
name: Fetch release asset
uses: dsaltares/fetch-gh-release-asset@1.1.2
with:
version: "tags/${{ inputs.release_version }}"
regex: true
file: "dragonfly-.*\\.tar\\.gz"
target: 'releases/'
token: ${{ secrets.GH_TOKEN }}
- if: inputs.fetch_release
name: Extract artifacts
run: |
echo "Event prerelease ${{ github.event.release.prerelease }}"
echo "Input prerelease ${{ github.event.inputs.PRERELEASE }}"
ls -l
ls -l releases
for f in releases/*.tar.gz; do tar xvfz $f -C releases; done
rm releases/*.tar.gz
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v3
with:
platforms: arm64,amd64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ inputs.registry }}
username: ${{ inputs.registry_username }}
password: ${{ secrets.registry_password }}
- name: Docker meta
id: metadata
uses: docker/metadata-action@v5
with:
images: |
${{ inputs.image }}
tags: |
# will set tag 'latest' for ubuntu build on production push
# inputs.tag_latest will be true on when triggered by docker-release.yml
# matrix.tag_main will only be true for ubuntu flavor
type=raw,value=latest,enable=${{ matrix.tag_main && inputs.tag_latest }}
# set a tag like 'alpine' or 'ubuntu', if we're setting 'latest' during this build as well
type=raw,value=${{ matrix.flavor }},enable=${{ inputs.tag_latest }}
# will set tag like 'alpha' for ubuntu build, if inputs.tag is not empty
# but will set the non-flavored tag only, if matrix.tag_main is true
type=raw,value=${{ inputs.tag }},enable=${{ inputs.tag != 'false' && matrix.tag_main }}
# will set tag like 'alpha-(ubuntu|alpine)', if inputs.tag is not empty
type=raw,value=${{ inputs.tag }}-${{ matrix.flavor }},enable=${{ inputs.tag != 'false' }}
# will set tag like 'v0.12.0' for ubuntu build, if inputs.release_version is not empty
# but will set the non-flavored tag only, if matrix.tag_main is true
type=raw,value=${{ inputs.release_version }},enable=${{ matrix.tag_main && inputs.release_version != '' }}
# will set tag like 'v0.12.0-(ubuntu|alpine)', if inputs.release_version is not empty
type=raw,value=${{ inputs.release_version }}-${{ matrix.flavor }},enable=${{ inputs.release_version != '' }}
# build is broken based on platforms as load: true is not supported with multi-platform builds
- if: ${{ hashFiles(format('{0}-{1}', matrix.dockerfile, inputs.build_type)) }}
name: Build release image for amd64
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
build-args: |
QEMU_CPU=max,pauth-impdef=on
push: false
tags: |
${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
file: ${{ matrix.dockerfile }}-${{ inputs.build_type }}
cache-from: type=gha
cache-to: type=gha,mode=max
# only load AMD64 image for testing
load: true
- if: ${{ hashFiles(format('{0}-{1}', matrix.dockerfile, inputs.build_type)) }}
name: Build release image for arm64
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
build-args: |
QEMU_CPU=max,pauth-impdef=on
push: false
tags: |
${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
file: ${{ matrix.dockerfile }}-${{ inputs.build_type }}
cache-from: type=gha
cache-to: type=gha,mode=max
- if: ${{ hashFiles(format('{0}-{1}', matrix.dockerfile, inputs.build_type)) }}
name: Test Docker run
run: |
# docker run with port-forwarding
# install redis-tools
sudo apt-get install redis-tools -y
tag="${{ steps.metadata.outputs.tags }}"
# use the first tag if multiple tags are provided
if [[ $(echo "${tag}" | wc -l) -gt 1 ]]; then
echo "Multiple tags provided, using the first one"
tag=$(echo "${tag}" | head -n 1)
fi
echo "Testing ${tag}"
docker run -d -p 6379:6379 ${tag}
sleep 5
# ping with redis-cli and expect PONG
if [[ $(redis-cli -h localhost ping) != "PONG" ]]; then
echo "Redis ping failed"
exit 1
else
echo "Redis ping succeeded"
exit 0
fi
- if: ${{ hashFiles(format('{0}-{1}', matrix.dockerfile, inputs.build_type)) }}
name: Push release image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
build-args: |
QEMU_CPU=max,pauth-impdef=on
tags: |
${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
file: ${{ matrix.dockerfile }}-${{ inputs.build_type }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM ghcr.io/romange/ubuntu-dev:20 as builder
FROM ghcr.io/romange/ubuntu-dev:20 AS builder
ARG TARGETPLATFORM