mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-06-01 13:24:04 +02:00
Enhance GitHub Actions workflow with addons_json input
Added input parameter 'addons_json' for specifying add-on directories in workflow. Enhanced conditional checks and improved error handling for processing changed add-ons.
This commit is contained in:
472
.github/workflows/onpush_builder.yaml
vendored
472
.github/workflows/onpush_builder.yaml
vendored
@@ -1,10 +1,15 @@
|
|||||||
# yamllint disable rule:line-length
|
# yamllint disable rule:line-length
|
||||||
# inspired by Poeschl/Hassio-Addons, optimized by ChatGPT
|
|
||||||
---
|
---
|
||||||
name: Builder
|
name: Builder
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
addons_json:
|
||||||
|
description: JSON array of add-on directories to process. If empty, all add-ons are discovered.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@@ -12,81 +17,102 @@ on:
|
|||||||
- "**/config.*"
|
- "**/config.*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# 1. Detect which add-on folders changed (by config.json|yaml|yml modification)
|
|
||||||
detect-changed-addons:
|
detect-changed-addons:
|
||||||
if: ${{ github.repository_owner == 'alexbelgium' && !contains(github.event.head_commit.message, 'nobuild') }}
|
if: >-
|
||||||
|
${{
|
||||||
|
github.repository_owner == 'alexbelgium' &&
|
||||||
|
(github.event_name != 'push' || !contains(github.event.head_commit.message, 'nobuild'))
|
||||||
|
}}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
changedAddons: ${{ steps.find_addons.outputs.changed_addons }}
|
changedAddons: ${{ steps.find_addons.outputs.changed_addons }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Find changed addon directories
|
with:
|
||||||
id: find_addons
|
fetch-depth: 0
|
||||||
run: |
|
|
||||||
git fetch origin "${{ github.event.before }}" || true
|
- name: Find add-on directories to process
|
||||||
changed_config_files=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" | grep -E '^[^/]+/config\.(json|ya?ml)$' || true)
|
id: find_addons
|
||||||
echo "Changed config files:"
|
env:
|
||||||
echo "$changed_config_files"
|
INPUT_ADDONS_JSON: ${{ inputs.addons_json }}
|
||||||
changed_addons=$(printf '%s' "$changed_config_files" | awk -F/ '{print $1}' | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
run: |
|
||||||
echo "Changed addons: $changed_addons"
|
set -euo pipefail
|
||||||
echo "changed_addons=$changed_addons" >> "$GITHUB_OUTPUT"
|
|
||||||
|
if [ -n "${INPUT_ADDONS_JSON:-}" ]; then
|
||||||
|
if ! jq -e 'type == "array" and all(.[]; type == "string")' <<<"$INPUT_ADDONS_JSON" >/dev/null; then
|
||||||
|
echo "workflow_call input addons_json must be a JSON array of strings" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
changed_addons=$(jq -c 'map(select(length > 0)) | unique' <<<"$INPUT_ADDONS_JSON")
|
||||||
|
elif [ "${{ github.event_name }}" = "push" ]; then
|
||||||
|
before="${{ github.event.before }}"
|
||||||
|
if [ -n "$before" ] && [ "$before" != "0000000000000000000000000000000000000000" ]; then
|
||||||
|
git fetch --no-tags --depth=1 origin "$before" || true
|
||||||
|
changed_config_files=$(git diff --name-only "$before" "${{ github.sha }}" | grep -E '^[^/]+/config\.(json|ya?ml)$' || true)
|
||||||
|
else
|
||||||
|
changed_config_files=$(git diff-tree --no-commit-id --name-only -r "${{ github.sha }}" | grep -E '^[^/]+/config\.(json|ya?ml)$' || true)
|
||||||
|
fi
|
||||||
|
echo "Changed config files:"
|
||||||
|
printf '%s\n' "$changed_config_files"
|
||||||
|
changed_addons=$(printf '%s\n' "$changed_config_files" | awk -F/ 'NF { print $1 }' | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||||
|
else
|
||||||
|
changed_addons=$(find . -maxdepth 2 \( -name 'config.json' -o -name 'config.yaml' -o -name 'config.yml' \) -printf '%h\n' | sed 's#^\./##' | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Changed add-ons: $changed_addons"
|
||||||
|
echo "changed_addons=${changed_addons:-[]}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
# 2. Pre-build sanitize: normalize spaces, fix script permissions, single commit per add-on
|
|
||||||
prebuild-sanitize:
|
prebuild-sanitize:
|
||||||
if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
|
if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
|
||||||
needs: detect-changed-addons
|
needs: detect-changed-addons
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
permissions:
|
||||||
matrix:
|
contents: write
|
||||||
addon: ${{ fromJSON(needs.detect-changed-addons.outputs.changedAddons) }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
# ────────────────────────────────────────────────────────────────
|
with:
|
||||||
# 1. Replace non-printable Unicode spaces ␣ and
|
fetch-depth: 0
|
||||||
# convert Windows line endings (CRLF) → Unix (LF)
|
|
||||||
# ────────────────────────────────────────────────────────────────
|
- name: Sanitize text files and script permissions
|
||||||
- name: Sanitize text files (Unicode spaces + CRLF → LF)
|
env:
|
||||||
|
ADDONS_JSON: ${{ needs.detect-changed-addons.outputs.changedAddons }}
|
||||||
run: |
|
run: |
|
||||||
cd "${{ matrix.addon }}"
|
set -euo pipefail
|
||||||
|
|
||||||
UNICODE_SPACES_REGEX=$'[\u00A0\u2002\u2003\u2007\u2008\u2009\u202F\u205F\u3000\u200B]'
|
UNICODE_SPACES_REGEX=$'[\u00A0\u2002\u2003\u2007\u2008\u2009\u202F\u205F\u3000\u200B]'
|
||||||
|
mapfile -t addons < <(jq -r '.[]' <<<"$ADDONS_JSON")
|
||||||
|
|
||||||
find . -type f | while read -r file; do
|
for addon in "${addons[@]}"; do
|
||||||
MIME_TYPE=$(file --mime-type -b "$file")
|
echo "Sanitizing ${addon}"
|
||||||
[[ "$MIME_TYPE" != text/* ]] && continue
|
cd "$GITHUB_WORKSPACE/$addon"
|
||||||
|
|
||||||
echo "Sanitizing $file"
|
while IFS= read -r -d '' file; do
|
||||||
# Edit in place, preserving file permissions and metadata
|
mime_type=$(file --mime-type -b "$file")
|
||||||
perl -i -CSD -pe "
|
[[ "$mime_type" != text/* ]] && continue
|
||||||
s/${UNICODE_SPACES_REGEX}/ /g; # space normalization
|
|
||||||
s/\r$//; # CRLF → LF
|
perl -i -CSD -pe "
|
||||||
" "$file"
|
s/${UNICODE_SPACES_REGEX}/ /g;
|
||||||
|
s/\r$//;
|
||||||
|
" "$file"
|
||||||
|
done < <(find . -type f -print0)
|
||||||
|
|
||||||
|
find . -type f -iname '*.sh' -exec chmod u+x {} \;
|
||||||
done
|
done
|
||||||
# ────────────────────────────────────────────────────────────────
|
|
||||||
# 2. Ensure every *.sh script is executable
|
|
||||||
# ────────────────────────────────────────────────────────────────
|
|
||||||
- name: Make all .sh scripts executable
|
|
||||||
run: |
|
|
||||||
cd "${{ matrix.addon }}"
|
|
||||||
find . -type f -iname "*.sh" -exec chmod u+x {} \;
|
|
||||||
# ────────────────────────────────────────────────────────────────
|
|
||||||
# 3. Verify nothing with mixed line endings slipped through
|
|
||||||
# ────────────────────────────────────────────────────────────────
|
|
||||||
- name: Assert no mixed CRLF/LF remain
|
- name: Assert no mixed CRLF/LF remain
|
||||||
uses: ymwymw/check-mixed-line-endings@v2
|
uses: ymwymw/check-mixed-line-endings@v2
|
||||||
# ────────────────────────────────────────────────────────────────
|
|
||||||
# 4. Commit any changes we made
|
- name: Commit sanitize changes
|
||||||
# ────────────────────────────────────────────────────────────────
|
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||||
- name: Commit if needed
|
|
||||||
uses: EndBug/add-and-commit@v10
|
uses: EndBug/add-and-commit@v10
|
||||||
with:
|
with:
|
||||||
commit: -u
|
commit: -u
|
||||||
message: "GitHub bot: sanitize (spaces + LF endings) & chmod"
|
message: "GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild]"
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
pull: --rebase --autostash
|
pull: --rebase --autostash
|
||||||
fetch: --tags --force
|
fetch: --tags --force
|
||||||
|
|
||||||
# 3. Lint add-on configs
|
|
||||||
lint_config:
|
lint_config:
|
||||||
if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
|
if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
|
||||||
needs: [detect-changed-addons, prebuild-sanitize]
|
needs: [detect-changed-addons, prebuild-sanitize]
|
||||||
@@ -102,14 +128,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: "./${{ matrix.addon }}"
|
path: "./${{ matrix.addon }}"
|
||||||
|
|
||||||
# 4. Build images for changed addons/arches
|
|
||||||
# Uses composable actions from home-assistant/builder (2026.03.2+)
|
|
||||||
# which replaced the deprecated home-assistant/builder action.
|
|
||||||
build:
|
build:
|
||||||
if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
|
if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
|
||||||
needs: [detect-changed-addons, lint_config]
|
needs: [detect-changed-addons, lint_config]
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
environment: CR_PAT
|
|
||||||
name: Build ${{ matrix.arch }} ${{ matrix.addon }} add-on
|
name: Build ${{ matrix.arch }} ${{ matrix.addon }} add-on
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -118,7 +140,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
addon: ${{ fromJSON(needs.detect-changed-addons.outputs.changedAddons) }}
|
addon: ${{ fromJSON(needs.detect-changed-addons.outputs.changedAddons) }}
|
||||||
arch: ["aarch64", "amd64"]
|
arch: [amd64, aarch64]
|
||||||
include:
|
include:
|
||||||
- arch: amd64
|
- arch: amd64
|
||||||
runner: ubuntu-24.04
|
runner: ubuntu-24.04
|
||||||
@@ -128,14 +150,18 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Resolve Symlinks (in repo)
|
|
||||||
|
- name: Resolve symlinks in repository copy
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
find . -type l | while read -r link; do
|
find . -type l | while read -r link; do
|
||||||
target="$(readlink -f "$link")"
|
target=$(readlink -f "$link" || true)
|
||||||
if [ -z "$target" ]; then
|
if [ -z "$target" ]; then
|
||||||
echo "Skipping broken symlink: $link"
|
echo "Skipping broken symlink: $link"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm "$link"
|
rm "$link"
|
||||||
if [ -d "$target" ]; then
|
if [ -d "$target" ]; then
|
||||||
mkdir -p "$link"
|
mkdir -p "$link"
|
||||||
@@ -144,105 +170,137 @@ jobs:
|
|||||||
cp "$target" "$link"
|
cp "$target" "$link"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
- name: Get addon info
|
|
||||||
|
- name: Install PyYAML
|
||||||
|
run: python3 -m pip install --disable-pip-version-check pyyaml
|
||||||
|
|
||||||
|
- name: Read add-on metadata
|
||||||
id: info
|
id: info
|
||||||
env:
|
env:
|
||||||
ADDON: ${{ matrix.addon }}
|
ADDON: ${{ matrix.addon }}
|
||||||
ARCH: ${{ matrix.arch }}
|
ARCH: ${{ matrix.arch }}
|
||||||
|
REPOSITORY: ${{ github.repository }}
|
||||||
run: |
|
run: |
|
||||||
cd "./$ADDON"
|
set -euo pipefail
|
||||||
|
|
||||||
# --- Read config file (json/yaml/yml) ---
|
python3 - <<'PY'
|
||||||
version="" image="" name="" description="" arch_list="[]"
|
import json
|
||||||
for ext in json yaml yml; do
|
import os
|
||||||
if [ -f "config.$ext" ]; then
|
from datetime import datetime, timezone
|
||||||
if [ "$ext" = "json" ]; then
|
from pathlib import Path
|
||||||
version=$(jq -r '.version // ""' "config.$ext")
|
|
||||||
image=$(jq -r '.image // ""' "config.$ext")
|
import yaml
|
||||||
name=$(jq -r '.name // ""' "config.$ext")
|
|
||||||
description=$(jq -r '.description // ""' "config.$ext")
|
addon = os.environ["ADDON"]
|
||||||
arch_list=$(jq -c '.arch // []' "config.$ext")
|
arch = os.environ["ARCH"]
|
||||||
else
|
repository = os.environ["REPOSITORY"]
|
||||||
version=$(grep -m1 '^version:' "config.$ext" | sed 's/^version:[[:space:]]*//' | tr -d "\"'")
|
addon_dir = Path(addon)
|
||||||
image=$(grep -m1 '^image:' "config.$ext" | sed 's/^image:[[:space:]]*//' | tr -d "\"'")
|
output_path = Path(os.environ["GITHUB_OUTPUT"])
|
||||||
name=$(grep -m1 '^name:' "config.$ext" | sed 's/^name:[[:space:]]*//' | tr -d "\"'")
|
|
||||||
description=$(grep -m1 '^description:' "config.$ext" | sed 's/^description:[[:space:]]*//' | tr -d "\"'")
|
|
||||||
arch_list=$(awk '
|
def load_file(path: Path):
|
||||||
/^arch:/ { inblock=1; next }
|
text = path.read_text(encoding="utf-8")
|
||||||
/^[^[:space:]]/ { if (inblock) exit }
|
if path.suffix == ".json":
|
||||||
inblock && /^[[:space:]]/ { print }
|
return json.loads(text)
|
||||||
' "config.$ext" | sed 's/^ *- *//' | tr -d "\"'" | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
return yaml.safe_load(text) or {}
|
||||||
fi
|
|
||||||
break
|
|
||||||
fi
|
def first_existing(*names: str):
|
||||||
done
|
for name in names:
|
||||||
|
path = addon_dir / name
|
||||||
|
if path.exists():
|
||||||
|
return path
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
config_path = first_existing("config.json", "config.yaml", "config.yml")
|
||||||
|
if config_path is None:
|
||||||
|
raise SystemExit(f"No config file found in {addon}")
|
||||||
|
|
||||||
|
build_path = first_existing("build.json", "build.yaml", "build.yml")
|
||||||
|
config = load_file(config_path)
|
||||||
|
build = load_file(build_path) if build_path else {}
|
||||||
|
|
||||||
|
build_from_map = build.get("build_from") or {}
|
||||||
|
arch_list = list(build_from_map.keys()) if build_from_map else list(config.get("arch") or [])
|
||||||
|
build_arch = arch in arch_list
|
||||||
|
|
||||||
|
image_raw = str(config.get("image") or "").strip()
|
||||||
|
image = image_raw.replace("{arch}", arch)
|
||||||
|
version = str(config.get("version") or "").strip()
|
||||||
|
name = str(config.get("name") or "").strip()
|
||||||
|
description = str(config.get("description") or "").strip()
|
||||||
|
url = str(config.get("url") or "").strip()
|
||||||
|
build_from = str(build_from_map.get(arch) or "").strip()
|
||||||
|
build_date = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
||||||
|
|
||||||
|
dockerfile = addon_dir / f"Dockerfile.{arch}"
|
||||||
|
if dockerfile.exists():
|
||||||
|
dockerfile_name = dockerfile.name
|
||||||
|
has_dockerfile = True
|
||||||
|
else:
|
||||||
|
dockerfile = addon_dir / "Dockerfile"
|
||||||
|
dockerfile_name = dockerfile.name
|
||||||
|
has_dockerfile = dockerfile.exists()
|
||||||
|
|
||||||
|
labels = [
|
||||||
|
f"io.hass.name={name}",
|
||||||
|
f"io.hass.description={description}",
|
||||||
|
"io.hass.type=addon",
|
||||||
|
]
|
||||||
|
if url:
|
||||||
|
labels.append(f"io.hass.url={url}")
|
||||||
|
|
||||||
|
build_args = [
|
||||||
|
f"BUILD_DATE={build_date}",
|
||||||
|
f"BUILD_DESCRIPTION={description}",
|
||||||
|
f"BUILD_NAME={name}",
|
||||||
|
f"BUILD_REF={os.environ.get('GITHUB_SHA', '')}",
|
||||||
|
f"BUILD_REPOSITORY={repository}",
|
||||||
|
]
|
||||||
|
if build_from:
|
||||||
|
build_args.insert(0, f"BUILD_FROM={build_from}")
|
||||||
|
|
||||||
|
|
||||||
|
def write_output(key: str, value: str):
|
||||||
|
with output_path.open("a", encoding="utf-8") as fh:
|
||||||
|
print(f"{key}<<__EOF__", file=fh)
|
||||||
|
print(value, file=fh)
|
||||||
|
print("__EOF__", file=fh)
|
||||||
|
|
||||||
|
|
||||||
|
write_output("architectures", json.dumps(arch_list))
|
||||||
|
write_output("build_arch", "true" if build_arch else "false")
|
||||||
|
write_output("has_dockerfile", "true" if has_dockerfile else "false")
|
||||||
|
write_output("dockerfile", dockerfile_name)
|
||||||
|
write_output("image", image)
|
||||||
|
write_output("version", version)
|
||||||
|
write_output("name", name)
|
||||||
|
write_output("description", description)
|
||||||
|
write_output("url", url)
|
||||||
|
write_output("build_from", build_from)
|
||||||
|
write_output("build_date", build_date)
|
||||||
|
write_output("labels", "\n".join(labels))
|
||||||
|
write_output("build_args", "\n".join(build_args))
|
||||||
|
PY
|
||||||
|
|
||||||
# --- Read build config for build_from (json/yaml/yml) ---
|
- name: Explain skipped builds
|
||||||
build_from="" build_archs=""
|
if: steps.info.outputs.build_arch != 'true' || steps.info.outputs.has_dockerfile != 'true'
|
||||||
for ext in json yaml yml; do
|
run: |
|
||||||
if [ -f "build.$ext" ]; then
|
if [ "${{ steps.info.outputs.has_dockerfile }}" != 'true' ]; then
|
||||||
if [ "$ext" = "json" ]; then
|
echo "No Dockerfile or Dockerfile.${{ matrix.arch }} found in ${{ matrix.addon }}, skipping build."
|
||||||
build_from=$(jq -r ".build_from.\"$ARCH\" // \"\"" "build.$ext")
|
elif [ "${{ steps.info.outputs.build_arch }}" != 'true' ]; then
|
||||||
build_archs=$(jq -c '.build_from | keys' "build.$ext")
|
echo "${{ matrix.arch }} is not a valid architecture for ${{ matrix.addon }}, skipping build."
|
||||||
else
|
|
||||||
build_block=$(awk '
|
|
||||||
/^build_from:/ { inblock=1; next }
|
|
||||||
/^[^[:space:]]/ { if (inblock) exit }
|
|
||||||
inblock && /^[[:space:]]/ { print }
|
|
||||||
' "build.$ext")
|
|
||||||
build_from=$(printf '%s\n' "$build_block" | grep -E "^[[:space:]]*${ARCH}:" | sed "s/^[[:space:]]*${ARCH}:[[:space:]]*//" | tr -d "\"'")
|
|
||||||
build_archs=$(printf '%s\n' "$build_block" | sed 's/^[[:space:]]*//; s/:.*//' | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
|
||||||
fi
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Use build config architectures if available (overrides config arch list)
|
|
||||||
if [ -n "$build_archs" ] && [ "$build_archs" != "[]" ]; then
|
|
||||||
arch_list="$build_archs"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Resolve {arch} placeholder in image name
|
|
||||||
resolved_image="${image//\{arch\}/$ARCH}"
|
|
||||||
|
|
||||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "image=$resolved_image" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "name=$name" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "description=$description" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "architectures=$arch_list" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "build_from=$build_from" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "build_date=$(date --rfc-3339=seconds --utc)" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
echo "Addon: $ADDON, Arch: $ARCH"
|
|
||||||
echo " Version: $version"
|
|
||||||
echo " Image: $resolved_image"
|
|
||||||
echo " Build from: $build_from"
|
|
||||||
echo " Architectures: $arch_list"
|
|
||||||
- name: Check if Dockerfile exists
|
|
||||||
id: dockerfile_check
|
|
||||||
run: |
|
|
||||||
if [ -f "./${{ matrix.addon }}/Dockerfile" ]; then
|
|
||||||
echo "has_dockerfile=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "No Dockerfile found in ${{ matrix.addon }}, skipping build."
|
|
||||||
echo "has_dockerfile=false" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
- name: Check if add-on should be built for arch
|
|
||||||
id: check
|
|
||||||
run: |
|
|
||||||
if [[ "${{ steps.info.outputs.architectures }}" =~ ${{ matrix.arch }} ]]; then
|
|
||||||
echo "build_arch=true" >> "$GITHUB_OUTPUT";
|
|
||||||
else
|
|
||||||
echo "${{ matrix.arch }} is not a valid arch for ${{ matrix.addon }}, skipping build";
|
|
||||||
echo "build_arch=false" >> "$GITHUB_OUTPUT";
|
|
||||||
fi
|
|
||||||
- name: Build ${{ matrix.addon }} add-on
|
- name: Build ${{ matrix.addon }} add-on
|
||||||
if: steps.check.outputs.build_arch == 'true' && steps.dockerfile_check.outputs.has_dockerfile == 'true'
|
if: steps.info.outputs.build_arch == 'true' && steps.info.outputs.has_dockerfile == 'true'
|
||||||
uses: home-assistant/builder/actions/build-image@2026.03.2
|
uses: home-assistant/builder/actions/build-image@2026.03.2
|
||||||
with:
|
with:
|
||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
cache-gha-scope: ${{ matrix.addon }}
|
cache-gha-scope: ${{ matrix.addon }}-${{ matrix.arch }}
|
||||||
context: "./${{ matrix.addon }}"
|
context: ./${{ matrix.addon }}
|
||||||
|
file: ${{ steps.info.outputs.dockerfile }}
|
||||||
image: ${{ steps.info.outputs.image }}
|
image: ${{ steps.info.outputs.image }}
|
||||||
image-tags: |
|
image-tags: |
|
||||||
${{ steps.info.outputs.version }}
|
${{ steps.info.outputs.version }}
|
||||||
@@ -251,79 +309,121 @@ jobs:
|
|||||||
push: "true"
|
push: "true"
|
||||||
cosign: "false"
|
cosign: "false"
|
||||||
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
|
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-args: |
|
labels: ${{ steps.info.outputs.labels }}
|
||||||
BUILD_FROM=${{ steps.info.outputs.build_from }}
|
build-args: ${{ steps.info.outputs.build_args }}
|
||||||
BUILD_DATE=${{ steps.info.outputs.build_date }}
|
|
||||||
BUILD_DESCRIPTION=${{ steps.info.outputs.description }}
|
|
||||||
BUILD_NAME=${{ steps.info.outputs.name }}
|
|
||||||
BUILD_REF=${{ github.sha }}
|
|
||||||
BUILD_REPOSITORY=${{ github.repository }}
|
|
||||||
|
|
||||||
# 5. Update changelog if needed (for each changed add-on)
|
|
||||||
make-changelog:
|
make-changelog:
|
||||||
if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
|
if: >-
|
||||||
|
${{
|
||||||
|
github.event_name == 'push' &&
|
||||||
|
github.ref == 'refs/heads/master' &&
|
||||||
|
needs.detect-changed-addons.outputs.changedAddons != '' &&
|
||||||
|
needs.detect-changed-addons.outputs.changedAddons != '[]'
|
||||||
|
}}
|
||||||
needs: [detect-changed-addons, build]
|
needs: [detect-changed-addons, build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
permissions:
|
||||||
matrix:
|
contents: write
|
||||||
addon: ${{ fromJSON(needs.detect-changed-addons.outputs.changedAddons) }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Update changelog for minor versions
|
- name: Update changelog for minor versions
|
||||||
|
env:
|
||||||
|
ADDONS_JSON: ${{ needs.detect-changed-addons.outputs.changedAddons }}
|
||||||
run: |
|
run: |
|
||||||
echo "Starting"
|
set -euo pipefail
|
||||||
git pull || true
|
|
||||||
cd "${{ matrix.addon }}"
|
mapfile -t addons < <(jq -r '.[]' <<<"$ADDONS_JSON")
|
||||||
if [ -f config.yaml ]; then
|
|
||||||
version="$(sed -e '/version/!d' -e 's/.*version: //' config.yaml)"
|
for addon in "${addons[@]}"; do
|
||||||
elif [ -f config.json ]; then
|
echo "Updating changelog for ${addon}"
|
||||||
version="$(sed -e '/version/!d' -e 's/.*[^"]*"\([^"]*\)"/\1/' config.json)"
|
cd "$GITHUB_WORKSPACE/$addon"
|
||||||
version="${version//,}"
|
|
||||||
else
|
if [ -f config.yaml ]; then
|
||||||
exit 1
|
version=$(sed -n 's/^version:[[:space:]]*//p' config.yaml | head -n 1)
|
||||||
fi
|
elif [ -f config.yml ]; then
|
||||||
version="${version//\"/}"
|
version=$(sed -n 's/^version:[[:space:]]*//p' config.yml | head -n 1)
|
||||||
version="${version//\'/}"
|
elif [ -f config.json ]; then
|
||||||
version="$(echo "$version" | xargs)"
|
version=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' config.json | head -n 1)
|
||||||
if [[ "$version" == *"test"* ]]; then exit 0; fi
|
else
|
||||||
touch CHANGELOG.md
|
echo "No config file found in ${addon}" >&2
|
||||||
if ! grep -q "$version" CHANGELOG.md; then
|
exit 1
|
||||||
first_line="$(sed -n '/./p' CHANGELOG.md | head -n 1)"
|
|
||||||
if [[ "$first_line" != "-"* ]]; then
|
|
||||||
sed -i "1i\- Minor bugs fixed" CHANGELOG.md
|
|
||||||
fi
|
fi
|
||||||
sed -i "1i\## $version ($(date '+%d-%m-%Y'))" CHANGELOG.md
|
|
||||||
fi
|
version=${version//\"/}
|
||||||
- name: Commit if needed
|
version=${version//\'/}
|
||||||
|
version=$(echo "$version" | xargs)
|
||||||
|
|
||||||
|
if [[ "$version" == *test* ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch CHANGELOG.md
|
||||||
|
if ! grep -q "^## ${version} (" CHANGELOG.md; then
|
||||||
|
first_line=$(sed -n '/./p' CHANGELOG.md | head -n 1 || true)
|
||||||
|
if [[ -n "$first_line" && "$first_line" != -* ]]; then
|
||||||
|
sed -i '1i\- Minor bugs fixed' CHANGELOG.md
|
||||||
|
elif [[ -z "$first_line" ]]; then
|
||||||
|
printf '%s\n' '- Minor bugs fixed' > CHANGELOG.md
|
||||||
|
fi
|
||||||
|
sed -i "1i\## ${version} ($(date '+%d-%m-%Y'))" CHANGELOG.md
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Commit changelog changes
|
||||||
uses: EndBug/add-and-commit@v10
|
uses: EndBug/add-and-commit@v10
|
||||||
with:
|
with:
|
||||||
commit: -u
|
commit: -u
|
||||||
message: "GitHub bot: changelog"
|
message: "GitHub bot: changelog [nobuild]"
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
pull: --rebase --autostash
|
pull: --rebase --autostash
|
||||||
fetch: --force
|
fetch: --force
|
||||||
push: --force
|
push: --force
|
||||||
|
|
||||||
# 6. Revert if workflow fails
|
|
||||||
revert-on-failure:
|
revert-on-failure:
|
||||||
if: failure()
|
if: >-
|
||||||
|
${{
|
||||||
|
failure() &&
|
||||||
|
github.event_name == 'push' &&
|
||||||
|
github.ref == 'refs/heads/master' &&
|
||||||
|
github.repository_owner == 'alexbelgium'
|
||||||
|
}}
|
||||||
needs: [detect-changed-addons, prebuild-sanitize, lint_config, build]
|
needs: [detect-changed-addons, prebuild-sanitize, lint_config, build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Revert the triggering commit
|
- name: Revert commits from this failed push
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
git config --global user.name "GitHub Actions"
|
git config --global user.name "GitHub Actions"
|
||||||
git config --global user.email "actions@github.com"
|
git config --global user.email "actions@github.com"
|
||||||
git fetch origin
|
git fetch origin
|
||||||
git checkout master
|
git checkout master
|
||||||
git pull origin master
|
git pull --ff-only origin master
|
||||||
git revert --no-commit ${{ github.sha }}
|
|
||||||
git commit -m "Revert '${{ github.event.head_commit.message }}' [nobuild]"
|
before="${{ github.event.before }}"
|
||||||
git push origin master
|
if [ -n "$before" ] && [ "$before" != "0000000000000000000000000000000000000000" ]; then
|
||||||
|
mapfile -t commits < <(git rev-list "${before}..HEAD")
|
||||||
|
else
|
||||||
|
commits=("${{ github.sha }}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#commits[@]}" -eq 0 ]; then
|
||||||
|
echo "Nothing to revert."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for commit in "${commits[@]}"; do
|
||||||
|
git revert --no-edit "$commit"
|
||||||
|
done
|
||||||
|
|
||||||
|
git push origin HEAD:master
|
||||||
|
|||||||
Reference in New Issue
Block a user