Deploy hass-addon-sunsynk-multi to ghcr.io #32
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Deploy hass-addon-sunsynk-multi to ghcr.io | |
on: | |
release: | |
types: | |
- published | |
workflow_run: | |
workflows: ["CI"] | |
branches: [main] | |
types: | |
- completed | |
workflow_dispatch: {} | |
env: | |
REGISTRY_IMAGE: ghcr.io/${{ github.repository_owner }}/hass-addon-sunsynk-multi | |
jobs: | |
ci-failure: | |
runs-on: ubuntu-latest | |
if: | | |
github.event_name == 'workflow_run' && | |
github.event.workflow_run.conclusion == 'failure' | |
steps: | |
- run: echo 'CI failed' | |
- run: exit 1 | |
information: | |
name: Gather add-on information | |
runs-on: ubuntu-latest | |
if: | | |
github.event_name == 'workflow_dispatch' || | |
github.event_name == 'release' || | |
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') | |
outputs: | |
architectures: ${{ steps.information.outputs.architectures }} | |
build: ${{ steps.information.outputs.build }} | |
description: ${{ steps.information.outputs.description }} | |
environment: ${{ steps.release.outputs.environment }} | |
name: ${{ steps.information.outputs.name }} | |
slug: ${{ steps.information.outputs.slug }} | |
target: ${{ steps.information.outputs.target }} | |
version: ${{ steps.release.outputs.version }} | |
steps: | |
- name: ⤵️ Check out code from GitHub | |
uses: actions/checkout@v4 | |
- name: 🚀 Run add-on information | |
id: information | |
uses: frenck/[email protected] | |
with: | |
path: ./hass-addon-sunsynk-multi | |
- name: ℹ️ Gather version and environment | |
id: release | |
run: | | |
sha="${{ github.sha }}" | |
# Default values | |
environment="edge" | |
version="${sha:0:7}" | |
# Check if it's a release event | |
if [[ "${{ github.event_name }}" == "release" ]]; then | |
version="${{ github.event.release.tag_name }}" # Lowercase the tag name | |
version="${version#v}" # Remove 'v' prefix if present | |
environment="stable" | |
# Set environment to beta if it's a prerelease | |
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then | |
environment="beta" | |
fi | |
fi | |
# Output the environment and version | |
echo "environment=${environment}" >> "$GITHUB_OUTPUT" | |
echo "version=${version}" >> "$GITHUB_OUTPUT" | |
# Log the outputs | |
cat "$GITHUB_OUTPUT" >> $GITHUB_STEP_SUMMARY | |
build: | |
name: 👷 Build & Deploy ${{ matrix.platform }} ${{ needs.information.outputs.environment }}/${{ needs.information.outputs.version }} | |
runs-on: ubuntu-latest | |
needs: information | |
strategy: | |
fail-fast: false | |
matrix: | |
platform: | |
- linux/amd64 | |
- linux/arm/v6 | |
- linux/arm/v7 | |
- linux/arm64 | |
steps: | |
- name: ⤵️ Check out code from GitHub | |
uses: actions/checkout@v4 | |
- name: Prepare Files for Build | |
run: | | |
mkdir -p hass-addon-sunsynk-multi/sunsynk | |
cp -r src hass-addon-sunsynk-multi/sunsynk/ | |
cp setup.* README.md hass-addon-sunsynk-multi/sunsynk/ | |
- name: Set up the correct base image based on platform | |
run: | | |
case "${{ matrix.platform }}" in | |
linux/amd64) echo "BUILD_FROM=ghcr.io/home-assistant/amd64-base-python:3.12-alpine3.21" >> $GITHUB_ENV ;; | |
linux/arm64) echo "BUILD_FROM=ghcr.io/home-assistant/aarch64-base-python:3.12-alpine3.21" >> $GITHUB_ENV ;; | |
linux/arm/v6) echo "BUILD_FROM=ghcr.io/home-assistant/armhf-base-python:3.12-alpine3.21" >> $GITHUB_ENV ;; | |
linux/arm/v7) echo "BUILD_FROM=ghcr.io/home-assistant/armv7-base-python:3.12-alpine3.21" >> $GITHUB_ENV ;; | |
linux/i386) echo "BUILD_FROM=ghcr.io/home-assistant/i386-base-python:3.12-alpine3.21" >> $GITHUB_ENV ;; | |
esac | |
- name: Docker meta | |
id: meta | |
uses: docker/metadata-action@v5 | |
with: | |
images: ${{ env.REGISTRY_IMAGE }} | |
- name: Set up QEMU | |
uses: docker/setup-qemu-action@v3 | |
- name: Set up Docker Buildx | |
uses: docker/setup-buildx-action@v3 | |
- name: Cache Docker layers | |
uses: actions/cache@v4 | |
with: | |
path: /tmp/.buildx-cache # Cache path | |
key: ${{ runner.os }}-buildx-${{ github.sha }} # Unique key for cache | |
restore-keys: | | |
${{ runner.os }}-buildx- | |
- name: Login to GitHub Container Registry | |
uses: docker/login-action@v3 | |
with: | |
registry: ghcr.io | |
username: ${{ github.repository_owner }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: Prepare sanitized platform name | |
id: sanitize-platform | |
run: | | |
SANITIZED_PLATFORM=$(echo ${{ matrix.platform }} | sed 's/^linux\///; s/\///g') | |
echo "SANITIZED_PLATFORM=${SANITIZED_PLATFORM}" >> $GITHUB_ENV | |
echo "Sanitized platform name: ${SANITIZED_PLATFORM}" | |
- name: Build and push by digest | |
id: build | |
uses: docker/build-push-action@v6 | |
with: | |
context: ./hass-addon-sunsynk-multi # Specify the directory containing your Dockerfile | |
file: ./hass-addon-sunsynk-multi/Dockerfile # Specify the Dockerfile path within the context | |
platforms: ${{ matrix.platform }} | |
cache-from: type=local,src=/tmp/.buildx-cache # Use cache for build | |
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max # Save cache | |
labels: ${{ steps.meta.outputs.labels }} | |
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true | |
build-args: | | |
BUILD_FROM=${{ env.BUILD_FROM }} | |
- name: Export digest | |
run: | | |
mkdir -p /tmp/digests | |
digest="${{ steps.build.outputs.digest }}" | |
touch "/tmp/digests/${digest#sha256:}" | |
- name: Upload digest | |
uses: actions/upload-artifact@v4 | |
with: | |
name: digests-${{ env.SANITIZED_PLATFORM }} # Use the sanitized platform name | |
path: /tmp/digests/* | |
if-no-files-found: error | |
retention-days: 1 | |
- name: Save platform to file | |
id: save-platform | |
run: | | |
mkdir -p outputs | |
echo ${{ env.SANITIZED_PLATFORM }} >> outputs/${{ env.SANITIZED_PLATFORM }}.txt | |
- name: Artifact platform file for later | |
uses: actions/upload-artifact@v4 | |
with: | |
name: platform-outputs-${{ env.SANITIZED_PLATFORM }} | |
path: outputs/*.txt | |
if-no-files-found: error | |
retention-days: 1 | |
merge: | |
runs-on: ubuntu-latest | |
needs: | |
- information | |
- build | |
if: | | |
always() && | |
needs.information.result == 'success' && | |
needs.build.result != 'failure' | |
steps: | |
- name: Download digests | |
uses: actions/download-artifact@v4 | |
with: | |
path: /tmp/digests | |
pattern: digests-* | |
merge-multiple: true | |
- name: Set up Docker Buildx | |
uses: docker/setup-buildx-action@v3 | |
- name: Docker meta | |
id: meta | |
uses: docker/metadata-action@v5 | |
with: | |
images: ${{ env.REGISTRY_IMAGE }} | |
- name: Login to GitHub Container Registry | |
uses: docker/login-action@v3 | |
with: | |
registry: ghcr.io | |
username: ${{ github.repository_owner }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: Download platform outputs | |
uses: actions/download-artifact@v4 | |
with: | |
pattern: platform-outputs-* | |
path: outputs | |
merge-multiple: true | |
- name: Load outputs to variable | |
id: platforms_raw | |
run: | | |
ls outputs | |
# Replace newlines with commas, then remove the trailing comma | |
echo "value=$(cat outputs/*.txt | tr '\n' ',' | sed 's/,$//')" >> "$GITHUB_OUTPUT" | |
- name: Generate tags for multi-platform image | |
run: | | |
# Define base tags | |
TAGS=( | |
"-t ${{ env.REGISTRY_IMAGE }}:${{ needs.information.outputs.version }}" | |
"-t ${{ env.REGISTRY_IMAGE }}:${{ needs.information.outputs.environment }}" | |
) | |
# Get the list of platforms from the build job output | |
platforms="${{ steps.platforms_raw.outputs.value }}" | |
# Split the platforms and add platform-specific tags | |
IFS=',' read -ra PLATFORM_ARRAY <<< "$platforms" | |
for PLATFORM in "${PLATFORM_ARRAY[@]}"; do | |
TAGS+=("-t ${{ env.REGISTRY_IMAGE }}/${PLATFORM}:${{ needs.information.outputs.version }}") | |
TAGS+=("-t ${{ env.REGISTRY_IMAGE }}/${PLATFORM}:${{ needs.information.outputs.environment }}") | |
done | |
# Join tags into a string | |
TAGS_STRING=$(printf " %s" "${TAGS[@]}") | |
# Export the tags string to the environment for later use | |
echo "TAGS_STRING=${TAGS_STRING}" >> $GITHUB_ENV | |
# Log the generated tags for debugging | |
echo "Generated tags: ${TAGS_STRING}" | |
- name: Create manifest list and push | |
working-directory: /tmp/digests | |
run: | | |
# Function to retry with exponential backoff | |
function retry_with_backoff() { | |
local max_attempts=5 | |
local timeout=10 # Start with 10 seconds | |
local attempt=1 | |
while true; do | |
echo "Attempt $attempt of $max_attempts" | |
if "$@"; then | |
return 0 | |
fi | |
if [[ $attempt -ge $max_attempts ]]; then | |
echo "Failed after $attempt attempts" | |
return 1 | |
fi | |
echo "Attempt $attempt failed! Retrying in $timeout seconds..." | |
sleep $timeout | |
timeout=$((timeout * 2)) | |
attempt=$((attempt + 1)) | |
done | |
} | |
# Get all digests | |
digests=($(ls)) | |
echo "Found digests: ${digests[*]}" | |
# Create base manifest first | |
echo "Creating base manifest..." | |
retry_with_backoff docker buildx imagetools create \ | |
-t ${{ env.REGISTRY_IMAGE }}:${{ needs.information.outputs.version }} \ | |
-t ${{ env.REGISTRY_IMAGE }}:${{ needs.information.outputs.environment }} \ | |
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' "${digests[@]}") | |
echo "Waiting 30 seconds after base manifest creation before processing platform-specific tags..." | |
sleep 30 # Wait 30 seconds before processing platform-specific tags | |
# Process each platform separately | |
platforms="${{ steps.platforms_raw.outputs.value }}" | |
IFS=',' read -ra PLATFORM_ARRAY <<< "$platforms" | |
for PLATFORM in "${PLATFORM_ARRAY[@]}"; do | |
echo "Processing platform: $PLATFORM" | |
retry_with_backoff docker buildx imagetools create \ | |
-t ${{ env.REGISTRY_IMAGE }}/${PLATFORM}:${{ needs.information.outputs.version }} \ | |
-t ${{ env.REGISTRY_IMAGE }}/${PLATFORM}:${{ needs.information.outputs.environment }} \ | |
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' "${digests[@]}") | |
echo "Completed manifest for $PLATFORM" | |
echo "Waiting 30 seconds before processing next platform..." | |
sleep 30 # Wait 30 seconds between platforms | |
done | |
- name: Inspect image | |
run: | | |
# Function to retry with exponential backoff | |
function retry_with_backoff() { | |
local max_attempts=5 | |
local timeout=10 | |
local attempt=1 | |
while true; do | |
echo "Attempt $attempt of $max_attempts" | |
if "$@"; then | |
return 0 | |
fi | |
if [[ $attempt -ge $max_attempts ]]; then | |
echo "Failed after $attempt attempts" | |
return 1 | |
fi | |
echo "Attempt $attempt failed! Retrying in $timeout seconds..." | |
sleep $timeout | |
timeout=$((timeout * 2)) | |
attempt=$((attempt + 1)) | |
done | |
} | |
echo "Waiting 30 seconds for manifests to settle before starting inspections..." | |
sleep 30 | |
# Inspect base image first | |
echo "Inspecting base image..." | |
retry_with_backoff docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ needs.information.outputs.version }} | |
# Inspect each platform | |
platforms="${{ steps.platforms_raw.outputs.value }}" | |
IFS=',' read -ra PLATFORM_ARRAY <<< "$platforms" | |
for PLATFORM in "${PLATFORM_ARRAY[@]}"; do | |
echo "Inspecting platform: $PLATFORM" | |
retry_with_backoff docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}/${PLATFORM}:${{ needs.information.outputs.version }} | |
echo "Waiting 10 seconds before inspecting next platform..." | |
sleep 10 | |
done | |
publish_addon: | |
name: 🆕 Update addon version to ${{ needs.information.outputs.version }} | |
needs: [information, build, merge] | |
if: | | |
always() && | |
needs.information.result == 'success' && | |
needs.build.result != 'failure' && | |
needs.merge.result == 'success' | |
permissions: | |
contents: write | |
runs-on: ubuntu-latest | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
ref: main | |
- name: Configure Git | |
run: | | |
git config --global user.name 'github-actions[bot]' | |
git config --global user.email 'github-actions[bot]@users.noreply.github.com' | |
- name: Update versions and commit changes | |
run: | | |
# Pull latest changes with rebase strategy | |
git pull --rebase origin main | |
# Make the version updates | |
sed -i 's/version:.*/version: "${{ needs.information.outputs.version }}"/g' hass-addon-sunsynk-edge/config.yaml | |
if [[ "${{ needs.information.outputs.environment }}" == "stable" ]]; then | |
sed -i 's/version:.*/version: "${{ needs.information.outputs.version }}"/g' hass-addon-sunsynk-multi/config.yaml | |
fi | |
# Stage and commit changes | |
git add hass-addon-sunsynk-edge/config.yaml hass-addon-sunsynk-multi/config.yaml | |
git commit -m "Update addon version to ${{ needs.information.outputs.version }} [no ci]" || echo "No changes to commit" | |
- name: Push changes | |
run: | | |
# Try to push changes | |
for i in {1..3}; do | |
if git push origin main; then | |
echo "Successfully pushed changes" | |
exit 0 | |
else | |
echo "Push failed, attempting to rebase and retry..." | |
git pull --rebase origin main | |
fi | |
echo "Waiting 5 seconds before next push attempt..." | |
sleep 5 | |
done | |
echo "Failed to push after 3 attempts" | |
exit 1 |