Build images #2118
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: Build images | |
# yamllint disable-line rule:truthy | |
on: | |
workflow_dispatch: | |
release: | |
types: ["published"] | |
schedule: | |
- cron: "0 2 * * *" | |
env: | |
BUILD_TYPE: core | |
DEFAULT_PYTHON: "3.13" | |
PIP_TIMEOUT: 60 | |
UV_HTTP_TIMEOUT: 60 | |
UV_SYSTEM_PYTHON: "true" | |
jobs: | |
init: | |
name: Initialize build | |
if: github.repository_owner == 'home-assistant' | |
runs-on: ubuntu-latest | |
outputs: | |
architectures: ${{ steps.info.outputs.architectures }} | |
version: ${{ steps.version.outputs.version }} | |
channel: ${{ steps.version.outputs.channel }} | |
publish: ${{ steps.version.outputs.publish }} | |
steps: | |
- name: Checkout the repository | |
uses: actions/[email protected] | |
with: | |
fetch-depth: 0 | |
- name: Set up Python ${{ env.DEFAULT_PYTHON }} | |
uses: actions/[email protected] | |
with: | |
python-version: ${{ env.DEFAULT_PYTHON }} | |
- name: Get information | |
id: info | |
uses: home-assistant/actions/helpers/info@master | |
- name: Get version | |
id: version | |
uses: home-assistant/actions/helpers/version@master | |
with: | |
type: ${{ env.BUILD_TYPE }} | |
- name: Verify version | |
uses: home-assistant/actions/helpers/verify-version@master | |
with: | |
ignore-dev: true | |
- name: Fail if translations files are checked in | |
run: | | |
if [ -n "$(find homeassistant/components/*/translations -type f)" ]; then | |
echo "Translations files are checked in, please remove the following files:" | |
find homeassistant/components/*/translations -type f | |
exit 1 | |
fi | |
- name: Download Translations | |
run: python3 -m script.translations download | |
env: | |
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} | |
- name: Archive translations | |
shell: bash | |
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T - | |
- name: Upload translations | |
uses: actions/[email protected] | |
with: | |
name: translations | |
path: translations.tar.gz | |
if-no-files-found: error | |
build_base: | |
name: Build ${{ matrix.arch }} base core image | |
if: github.repository_owner == 'home-assistant' | |
needs: init | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
packages: write | |
id-token: write | |
strategy: | |
fail-fast: false | |
matrix: | |
arch: ${{ fromJson(needs.init.outputs.architectures) }} | |
steps: | |
- name: Checkout the repository | |
uses: actions/[email protected] | |
- name: Download nightly wheels of frontend | |
if: needs.init.outputs.channel == 'dev' | |
uses: dawidd6/action-download-artifact@v6 | |
with: | |
github_token: ${{secrets.GITHUB_TOKEN}} | |
repo: home-assistant/frontend | |
branch: dev | |
workflow: nightly.yaml | |
workflow_conclusion: success | |
name: wheels | |
- name: Download nightly wheels of intents | |
if: needs.init.outputs.channel == 'dev' | |
uses: dawidd6/action-download-artifact@v6 | |
with: | |
github_token: ${{secrets.GITHUB_TOKEN}} | |
repo: home-assistant/intents-package | |
branch: main | |
workflow: nightly.yaml | |
workflow_conclusion: success | |
name: package | |
- name: Set up Python ${{ env.DEFAULT_PYTHON }} | |
if: needs.init.outputs.channel == 'dev' | |
uses: actions/[email protected] | |
with: | |
python-version: ${{ env.DEFAULT_PYTHON }} | |
- name: Adjust nightly version | |
if: needs.init.outputs.channel == 'dev' | |
shell: bash | |
env: | |
UV_PRERELEASE: allow | |
run: | | |
python3 -m pip install "$(grep '^uv' < requirements.txt)" | |
uv pip install packaging tomli | |
uv pip install . | |
python3 script/version_bump.py nightly --set-nightly-version "${{ needs.init.outputs.version }}" | |
if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then | |
echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}" | |
frontend_version="${BASH_REMATCH[1]}" yq \ | |
--inplace e -o json \ | |
'.requirements = ["home-assistant-frontend=="+env(frontend_version)]' \ | |
homeassistant/components/frontend/manifest.json | |
sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \ | |
homeassistant/package_constraints.txt | |
sed -i "s|home-assistant-frontend==.*||" requirements_all.txt | |
fi | |
if [[ "$(ls home_assistant_intents*.whl)" =~ ^home_assistant_intents-(.*)-py3-none-any.whl$ ]]; then | |
echo "Found intents wheel, setting version to: ${BASH_REMATCH[1]}" | |
yq \ | |
--inplace e -o json \ | |
'del(.requirements[] | select(contains("home-assistant-intents")))' \ | |
homeassistant/components/conversation/manifest.json | |
intents_version="${BASH_REMATCH[1]}" yq \ | |
--inplace e -o json \ | |
'.requirements += ["home-assistant-intents=="+env(intents_version)]' \ | |
homeassistant/components/conversation/manifest.json | |
sed -i "s|home-assistant-intents==.*|home-assistant-intents==${BASH_REMATCH[1]}|" \ | |
homeassistant/package_constraints.txt | |
sed -i "s|home-assistant-intents==.*||" requirements_all.txt | |
fi | |
- name: Adjustments for armhf | |
if: matrix.arch == 'armhf' | |
run: | | |
# Pandas has issues building on armhf, it is expected they | |
# will drop the platform in the near future (they consider it | |
# "flimsy" on 386). The following packages depend on pandas, | |
# so we comment them out. | |
sed -i "s|env-canada|# env-canada|g" requirements_all.txt | |
sed -i "s|noaa-coops|# noaa-coops|g" requirements_all.txt | |
sed -i "s|pyezviz|# pyezviz|g" requirements_all.txt | |
sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt | |
- name: Download translations | |
uses: actions/[email protected] | |
with: | |
name: translations | |
- name: Extract translations | |
run: | | |
tar xvf translations.tar.gz | |
rm translations.tar.gz | |
- name: Write meta info file | |
shell: bash | |
run: | | |
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE | |
- name: Login to GitHub Container Registry | |
uses: docker/[email protected] | |
with: | |
registry: ghcr.io | |
username: ${{ github.repository_owner }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: Build base image | |
uses: home-assistant/[email protected] | |
with: | |
args: | | |
$BUILD_ARGS \ | |
--${{ matrix.arch }} \ | |
--cosign \ | |
--target /data \ | |
--generic ${{ needs.init.outputs.version }} | |
build_machine: | |
name: Build ${{ matrix.machine }} machine core image | |
if: github.repository_owner == 'home-assistant' | |
needs: ["init", "build_base"] | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
packages: write | |
id-token: write | |
strategy: | |
matrix: | |
machine: | |
- generic-x86-64 | |
- intel-nuc | |
- khadas-vim3 | |
- odroid-c2 | |
- odroid-c4 | |
- odroid-m1 | |
- odroid-n2 | |
- odroid-xu | |
- qemuarm | |
- qemuarm-64 | |
- qemux86 | |
- qemux86-64 | |
- raspberrypi | |
- raspberrypi2 | |
- raspberrypi3 | |
- raspberrypi3-64 | |
- raspberrypi4 | |
- raspberrypi4-64 | |
- raspberrypi5-64 | |
- tinker | |
- yellow | |
- green | |
steps: | |
- name: Checkout the repository | |
uses: actions/[email protected] | |
- name: Set build additional args | |
run: | | |
# Create general tags | |
if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then | |
echo "BUILD_ARGS=--additional-tag dev" >> $GITHUB_ENV | |
elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then | |
echo "BUILD_ARGS=--additional-tag beta" >> $GITHUB_ENV | |
else | |
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV | |
fi | |
- name: Login to GitHub Container Registry | |
uses: docker/[email protected] | |
with: | |
registry: ghcr.io | |
username: ${{ github.repository_owner }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: Build base image | |
uses: home-assistant/[email protected] | |
with: | |
args: | | |
$BUILD_ARGS \ | |
--target /data/machine \ | |
--cosign \ | |
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}" | |
publish_ha: | |
name: Publish version files | |
environment: ${{ needs.init.outputs.channel }} | |
if: github.repository_owner == 'home-assistant' | |
needs: ["init", "build_machine"] | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout the repository | |
uses: actions/[email protected] | |
- name: Initialize git | |
uses: home-assistant/actions/helpers/git-init@master | |
with: | |
name: ${{ secrets.GIT_NAME }} | |
email: ${{ secrets.GIT_EMAIL }} | |
token: ${{ secrets.GIT_TOKEN }} | |
- name: Update version file | |
uses: home-assistant/actions/helpers/version-push@master | |
with: | |
key: "homeassistant[]" | |
key-description: "Home Assistant Core" | |
version: ${{ needs.init.outputs.version }} | |
channel: ${{ needs.init.outputs.channel }} | |
- name: Update version file (stable -> beta) | |
if: needs.init.outputs.channel == 'stable' | |
uses: home-assistant/actions/helpers/version-push@master | |
with: | |
key: "homeassistant[]" | |
key-description: "Home Assistant Core" | |
version: ${{ needs.init.outputs.version }} | |
channel: beta | |
publish_container: | |
name: Publish meta container for ${{ matrix.registry }} | |
environment: ${{ needs.init.outputs.channel }} | |
if: github.repository_owner == 'home-assistant' | |
needs: ["init", "build_base"] | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
packages: write | |
id-token: write | |
strategy: | |
fail-fast: false | |
matrix: | |
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"] | |
steps: | |
- name: Checkout the repository | |
uses: actions/[email protected] | |
- name: Install Cosign | |
uses: sigstore/[email protected] | |
with: | |
cosign-release: "v2.2.3" | |
- name: Login to DockerHub | |
if: matrix.registry == 'docker.io/homeassistant' | |
uses: docker/[email protected] | |
with: | |
username: ${{ secrets.DOCKERHUB_USERNAME }} | |
password: ${{ secrets.DOCKERHUB_TOKEN }} | |
- name: Login to GitHub Container Registry | |
if: matrix.registry == 'ghcr.io/home-assistant' | |
uses: docker/[email protected] | |
with: | |
registry: ghcr.io | |
username: ${{ github.repository_owner }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: Build Meta Image | |
shell: bash | |
run: | | |
export DOCKER_CLI_EXPERIMENTAL=enabled | |
function create_manifest() { | |
local tag_l=${1} | |
local tag_r=${2} | |
local registry=${{ matrix.registry }} | |
docker manifest create "${registry}/home-assistant:${tag_l}" \ | |
"${registry}/amd64-homeassistant:${tag_r}" \ | |
"${registry}/i386-homeassistant:${tag_r}" \ | |
"${registry}/armhf-homeassistant:${tag_r}" \ | |
"${registry}/armv7-homeassistant:${tag_r}" \ | |
"${registry}/aarch64-homeassistant:${tag_r}" | |
docker manifest annotate "${registry}/home-assistant:${tag_l}" \ | |
"${registry}/amd64-homeassistant:${tag_r}" \ | |
--os linux --arch amd64 | |
docker manifest annotate "${registry}/home-assistant:${tag_l}" \ | |
"${registry}/i386-homeassistant:${tag_r}" \ | |
--os linux --arch 386 | |
docker manifest annotate "${registry}/home-assistant:${tag_l}" \ | |
"${registry}/armhf-homeassistant:${tag_r}" \ | |
--os linux --arch arm --variant=v6 | |
docker manifest annotate "${registry}/home-assistant:${tag_l}" \ | |
"${registry}/armv7-homeassistant:${tag_r}" \ | |
--os linux --arch arm --variant=v7 | |
docker manifest annotate "${registry}/home-assistant:${tag_l}" \ | |
"${registry}/aarch64-homeassistant:${tag_r}" \ | |
--os linux --arch arm64 --variant=v8 | |
docker manifest push --purge "${registry}/home-assistant:${tag_l}" | |
cosign sign --yes "${registry}/home-assistant:${tag_l}" | |
} | |
function validate_image() { | |
local image=${1} | |
if ! cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp https://github.com/home-assistant/core/.* "${image}"; then | |
echo "Invalid signature!" | |
exit 1 | |
fi | |
} | |
function push_dockerhub() { | |
local image=${1} | |
local tag=${2} | |
docker tag "ghcr.io/home-assistant/${image}:${tag}" "docker.io/homeassistant/${image}:${tag}" | |
docker push "docker.io/homeassistant/${image}:${tag}" | |
cosign sign --yes "docker.io/homeassistant/${image}:${tag}" | |
} | |
# Pull images from github container registry and verify signature | |
docker pull "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}" | |
docker pull "ghcr.io/home-assistant/i386-homeassistant:${{ needs.init.outputs.version }}" | |
docker pull "ghcr.io/home-assistant/armhf-homeassistant:${{ needs.init.outputs.version }}" | |
docker pull "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}" | |
docker pull "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}" | |
validate_image "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}" | |
validate_image "ghcr.io/home-assistant/i386-homeassistant:${{ needs.init.outputs.version }}" | |
validate_image "ghcr.io/home-assistant/armhf-homeassistant:${{ needs.init.outputs.version }}" | |
validate_image "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}" | |
validate_image "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}" | |
if [[ "${{ matrix.registry }}" == "docker.io/homeassistant" ]]; then | |
# Upload images to dockerhub | |
push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}" | |
push_dockerhub "i386-homeassistant" "${{ needs.init.outputs.version }}" | |
push_dockerhub "armhf-homeassistant" "${{ needs.init.outputs.version }}" | |
push_dockerhub "armv7-homeassistant" "${{ needs.init.outputs.version }}" | |
push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}" | |
fi | |
# Create version tag | |
create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}" | |
# Create general tags | |
if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then | |
create_manifest "dev" "${{ needs.init.outputs.version }}" | |
elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then | |
create_manifest "beta" "${{ needs.init.outputs.version }}" | |
create_manifest "rc" "${{ needs.init.outputs.version }}" | |
else | |
create_manifest "stable" "${{ needs.init.outputs.version }}" | |
create_manifest "latest" "${{ needs.init.outputs.version }}" | |
create_manifest "beta" "${{ needs.init.outputs.version }}" | |
create_manifest "rc" "${{ needs.init.outputs.version }}" | |
# Create series version tag (e.g. 2021.6) | |
v="${{ needs.init.outputs.version }}" | |
create_manifest "${v%.*}" "${{ needs.init.outputs.version }}" | |
fi | |
build_python: | |
name: Build PyPi package | |
environment: ${{ needs.init.outputs.channel }} | |
needs: ["init", "build_base"] | |
runs-on: ubuntu-latest | |
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true' | |
steps: | |
- name: Checkout the repository | |
uses: actions/[email protected] | |
- name: Set up Python ${{ env.DEFAULT_PYTHON }} | |
uses: actions/[email protected] | |
with: | |
python-version: ${{ env.DEFAULT_PYTHON }} | |
- name: Download translations | |
uses: actions/[email protected] | |
with: | |
name: translations | |
- name: Extract translations | |
run: | | |
tar xvf translations.tar.gz | |
rm translations.tar.gz | |
- name: Build package | |
shell: bash | |
run: | | |
# Remove dist, build, and homeassistant.egg-info | |
# when build locally for testing! | |
pip install twine build | |
python -m build | |
- name: Upload package | |
shell: bash | |
run: | | |
export TWINE_USERNAME="__token__" | |
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}" | |
twine upload dist/* --skip-existing | |
hassfest-image: | |
name: Build and test hassfest image | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
packages: write | |
attestations: write | |
id-token: write | |
needs: ["init"] | |
if: github.repository_owner == 'home-assistant' | |
env: | |
HASSFEST_IMAGE_NAME: ghcr.io/home-assistant/hassfest | |
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }} | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
- name: Login to GitHub Container Registry | |
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 | |
with: | |
registry: ghcr.io | |
username: ${{ github.repository_owner }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: Build Docker image | |
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 | |
with: | |
context: . # So action will not pull the repository again | |
file: ./script/hassfest/docker/Dockerfile | |
load: true | |
tags: ${{ env.HASSFEST_IMAGE_TAG }} | |
- name: Run hassfest against core | |
run: docker run --rm -v ${{ github.workspace }}/homeassistant:/github/workspace/homeassistant ${{ env.HASSFEST_IMAGE_TAG }} --core-integrations-path=/github/workspace/homeassistant/components | |
- name: Push Docker image | |
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true' | |
id: push | |
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 | |
with: | |
context: . # So action will not pull the repository again | |
file: ./script/hassfest/docker/Dockerfile | |
push: true | |
tags: ${{ env.HASSFEST_IMAGE_TAG }},${{ env.HASSFEST_IMAGE_NAME }}:latest | |
- name: Generate artifact attestation | |
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true' | |
uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 | |
with: | |
subject-name: ${{ env.HASSFEST_IMAGE_NAME }} | |
subject-digest: ${{ steps.push.outputs.digest }} | |
push-to-registry: true |