GLPI Docker Images (#267: Support for custom cron jobs) #87
Workflow file for this run
This file contains hidden or 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: "GLPI Docker Images" | |
| run-name: "GLPI Docker Images (${{ inputs.glpi-version || (github.event.pull_request.number && format('#{0}: {1}', github.event.pull_request.number, github.event.pull_request.title)) || github.event.head_commit.message || github.sha }})" | |
| # Unified workflow for building GLPI Docker images | |
| # - workflow_call: Called by GLPI repo for releases and nightly builds | |
| # - workflow_dispatch: Manual builds from this repo | |
| # - push/pull_request: CI test builds when workflow changes (no push) | |
| env: | |
| # These will check Repository Variables first (perfect for fork), then use the default | |
| DOCKER_IMAGES_REPO: ${{ vars.DOCKER_IMAGES_REPO || 'glpi-project/docker-images' }} | |
| DOCKERHUB_IMAGE: ${{ vars.DOCKERHUB_IMAGE || 'glpi/glpi' }} | |
| GHCR_IMAGE: ${{ vars.GHCR_IMAGE || 'ghcr.io/glpi-project/glpi' }} | |
| on: | |
| # Reusable by other repos (e.g., glpi-project/glpi) | |
| workflow_call: | |
| inputs: | |
| glpi-version: | |
| description: "GLPI version or branch to build" | |
| required: true | |
| type: string | |
| push: | |
| description: "Whether to push images to registries" | |
| required: false | |
| type: boolean | |
| default: false | |
| patch-url: | |
| description: "URL to a .diff/.patch file to apply after source download, multiple patches can be separated by space" | |
| required: false | |
| type: string | |
| default: "" | |
| php-version: | |
| required: false | |
| type: string | |
| default: "8.5" | |
| image-tag: | |
| description: "Override image tag. If not set, computed from version." | |
| required: false | |
| type: string | |
| default: "" | |
| use-legacy-marketplace-path: | |
| description: "Use legacy marketplace path (/var/www/glpi/marketplace) for v10.x builds" | |
| required: false | |
| type: boolean | |
| default: false | |
| secrets: | |
| DOCKER_HUB_USERNAME: | |
| required: true | |
| DOCKER_HUB_TOKEN: | |
| required: true | |
| GHCR_USERNAME: | |
| required: true | |
| GHCR_ACCESS_TOKEN: | |
| required: true | |
| # CI: Test build on workflow changes (images are not pushed to docker registries) | |
| push: | |
| branches: | |
| - "main" | |
| paths: | |
| - ".github/workflows/glpi.yml" | |
| - "glpi/**" | |
| pull_request: | |
| paths: | |
| - ".github/workflows/glpi.yml" | |
| - "glpi/**" | |
| # Manual builds | |
| # Can be triggered by curl: | |
| # curl -X POST \ | |
| # -H "Accept: application/vnd.github.v3+json" \ | |
| # -H "Authorization: <access-token>" \ | |
| # https://api.github.com/repos/glpi-project/docker-images/actions/workflows/<workflow-id>/dispatches \ | |
| # -d '{"ref":"main", "inputs": { "glpi-version":"11.0.5", "push": true }}' | |
| workflow_dispatch: | |
| inputs: | |
| push: | |
| description: "Whether to push images to registries" | |
| required: false | |
| type: boolean | |
| default: false | |
| php-version: | |
| description: "PHP version to use for the build" | |
| required: false | |
| type: string | |
| default: "8.5" | |
| glpi-version: | |
| description: "GLPI version (tag: '11.0.5'), branch ('main', '11.0/bugfixes'), commit hash, or URL" | |
| required: true | |
| type: string | |
| image-tag: | |
| description: "Override image tag (glpi-version otherwise). i.e: '12.0.0-prebuild-20260615-1'" | |
| required: false | |
| type: string | |
| default: "" | |
| patch-url: | |
| description: "Optional patch URL(s) to apply. Multiple patches: separate with spaces" | |
| required: false | |
| type: string | |
| default: "" | |
| use-legacy-marketplace-path: | |
| description: "Use legacy marketplace path (/var/www/glpi/marketplace) for v10.x builds" | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| prepare: | |
| name: "Prepare" | |
| runs-on: "ubuntu-latest" | |
| outputs: | |
| artifact-prefix: ${{ steps.set-vars.outputs.artifact_prefix }} | |
| is-latest: ${{ steps.set-vars.outputs.is_latest }} | |
| is-latest-major: ${{ steps.set-vars.outputs.is_latest_major }} | |
| cache-key: ${{ steps.set-vars.outputs.cache_key }} | |
| image-version: ${{ steps.set-vars.outputs.image_version }} | |
| # Unified inputs for all trigger types | |
| glpi-version: ${{ steps.set-vars.outputs.glpi_version }} | |
| php-version: ${{ steps.set-vars.outputs.php_version }} | |
| patch-url: ${{ steps.set-vars.outputs.patch_url }} | |
| image-tag: ${{ steps.set-vars.outputs.image_tag }} | |
| push: ${{ steps.set-vars.outputs.push }} | |
| marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} | |
| steps: | |
| - name: "Set variables" | |
| id: "set-vars" | |
| run: | | |
| # Use inputs when provided, otherwise use defaults (for push/pull_request on this repo) | |
| GLPI_VERSION="${{ inputs.glpi-version || 'main' }}" | |
| PHP_VERSION="${{ inputs.php-version || '8.5' }}" | |
| PUSH="${{ inputs.push || 'false' }}" | |
| PATCH_URL="${{ inputs.patch-url || '' }}" | |
| IMAGE_TAG="${{ inputs.image-tag || '' }}" | |
| echo "glpi_version=$GLPI_VERSION" >> $GITHUB_OUTPUT | |
| echo "php_version=$PHP_VERSION" >> $GITHUB_OUTPUT | |
| echo "push=$PUSH" >> $GITHUB_OUTPUT | |
| echo "patch_url=$PATCH_URL" >> $GITHUB_OUTPUT | |
| echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT | |
| # Compute marketplace directory based on boolean use-legacy-marketplace-path input | |
| USE_LEGACY_MP_PATH="${{ inputs.use-legacy-marketplace-path || 'false' }}" | |
| if [[ "$USE_LEGACY_MP_PATH" = "true" ]]; then | |
| echo "marketplace_dir=/var/www/glpi/marketplace" >> $GITHUB_OUTPUT | |
| else | |
| echo "marketplace_dir=/var/glpi/marketplace" >> $GITHUB_OUTPUT | |
| fi | |
| # Determine if glpi-version is a semver tag (e.g., 10.0.18, 11.0.5-rc1) | |
| # vs a branch, commit hash, or URL | |
| IS_SEMVER=false | |
| if echo "$GLPI_VERSION" | grep --quiet --extended-regexp '^[0-9]+\.[0-9]+\.[0-9]+(\-\w+)?$'; then | |
| IS_SEMVER=true | |
| fi | |
| # Fetch the commit SHA for this version/branch (for cache-busting) | |
| # Same commit = cache hit, new commit = fresh build | |
| if echo "$GLPI_VERSION" | grep --quiet --extended-regexp '^https://'; then | |
| # For URLs, use a random value to force rebuild, as content behind a URL can change between builds. | |
| CACHE_KEY="$(head -200 /dev/urandom | sha1sum | cut --fields 1 --delimiter " ")" | |
| elif [[ "$GLPI_VERSION" = "latest" ]]; then | |
| # Resolve "latest" to actual tag first, then get commit SHA | |
| RESOLVED_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') | |
| CACHE_KEY=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$RESOLVED_TAG" | jq -r '.sha // empty') | |
| else | |
| CACHE_KEY=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$GLPI_VERSION" | jq -r '.sha // empty') | |
| fi | |
| echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT | |
| # Compute artifact prefix (unique per workflow call) | |
| if [[ "$IMAGE_TAG" != '' ]]; then | |
| ARTIFACT_PREFIX="$IMAGE_TAG" | |
| else | |
| # Replace slashes and colons with dashes for branches like 11.0/bugfixes | |
| ARTIFACT_PREFIX="$(echo "$GLPI_VERSION" | sed --regexp-extended 's|[/:]|-|g' | sed --regexp-extended 's|https?--||')" | |
| fi | |
| echo "artifact_prefix=$ARTIFACT_PREFIX" >> $GITHUB_OUTPUT | |
| # Compute image version for metadata-action | |
| if [[ "$IMAGE_TAG" != '' ]]; then | |
| IMAGE_VERSION="$IMAGE_TAG" | |
| else | |
| IMAGE_VERSION="$(echo "$GLPI_VERSION" | sed --regexp-extended 's|[/:]|-|g' | sed --regexp-extended 's|https?--||')" | |
| fi | |
| echo "image_version=$IMAGE_VERSION" >> $GITHUB_OUTPUT | |
| # Skip all version checks if: | |
| # - image-tag is provided (explicit override) | |
| # - glpi-version is not a semver (branch, commit, URL) | |
| if [[ "$IMAGE_TAG" != '' ]] || [[ "$IS_SEMVER" != "true" ]]; then | |
| echo "is_latest=false" >> $GITHUB_OUTPUT | |
| echo "is_latest_major=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Detect prerelease (contains -rc, -beta, -alpha, etc.) | |
| PRERELEASE_FLAG="$( echo "$GLPI_VERSION" | grep --perl-regexp --only-matching '(\-\w+)?$' )" | |
| if [[ -n "$PRERELEASE_FLAG" ]]; then | |
| echo "is_latest=false" >> $GITHUB_OUTPUT | |
| echo "is_latest_major=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Check if this is the latest release overall | |
| LATEST_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') | |
| if [[ "$GLPI_VERSION" = "$LATEST_TAG" ]]; then | |
| echo "is_latest=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_latest=false" >> $GITHUB_OUTPUT | |
| fi | |
| # Fetch all releases from GitHub API (non-prerelease only) for major series check | |
| RELEASES=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases?per_page=100" | \ | |
| jq -r '[.[] | select(.prerelease == false) | .tag_name]') | |
| # Check if this is the latest in its major series (e.g., latest 11.x.x) | |
| IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut --delimiter=. --fields=1) | |
| LATEST_IN_MAJOR=$(echo "$RELEASES" | jq -r --arg major "$IMAGE_VERSION_MAJOR" \ | |
| '[.[] | select(startswith($major + "."))] | .[0] // empty') | |
| if [[ "$GLPI_VERSION" = "$LATEST_IN_MAJOR" ]]; then | |
| echo "is_latest_major=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_latest_major=false" >> $GITHUB_OUTPUT | |
| fi | |
| build: | |
| name: "Build [${{ matrix.platform.suffix }}]" | |
| runs-on: "${{ matrix.platform.runner }}" | |
| needs: [prepare] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| - { arch: "linux/amd64", runner: "ubuntu-24.04", suffix: "amd64" } | |
| - { arch: "linux/arm64", runner: "ubuntu-24.04-arm", suffix: "arm64" } | |
| steps: | |
| - name: "Checkout" | |
| uses: "actions/checkout@v6" | |
| with: | |
| repository: ${{ env.DOCKER_IMAGES_REPO }} | |
| - name: "Prepare environment" | |
| run: | | |
| platform="${{ matrix.platform.arch }}" | |
| echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV | |
| - name: "Docker metadata" | |
| id: "meta" | |
| uses: "docker/metadata-action@v5" | |
| with: | |
| images: | | |
| ${{ env.DOCKERHUB_IMAGE }} | |
| ${{ env.GHCR_IMAGE }} | |
| - name: "Set up QEMU" | |
| uses: "docker/setup-qemu-action@v3" | |
| - name: "Set up Docker Buildx" | |
| uses: "docker/setup-buildx-action@v3" | |
| - name: "Login to DockerHub" | |
| if: ${{ needs.prepare.outputs.push == 'true' }} | |
| uses: "docker/login-action@v3" | |
| with: | |
| username: "${{ secrets.DOCKER_HUB_USERNAME }}" | |
| password: "${{ secrets.DOCKER_HUB_TOKEN }}" | |
| - name: "Login to Github container registry" | |
| if: ${{ needs.prepare.outputs.push == 'true' }} | |
| uses: "docker/login-action@v3" | |
| with: | |
| registry: "ghcr.io" | |
| username: "${{ secrets.GHCR_USERNAME }}" | |
| password: "${{ secrets.GHCR_ACCESS_TOKEN }}" | |
| - name: "Build and push by digest" | |
| id: "build" | |
| uses: "docker/build-push-action@v6" | |
| with: | |
| build-args: | | |
| GLPI_VERSION=${{ needs.prepare.outputs.glpi-version }} | |
| GLPI_PATCH_URL=${{ needs.prepare.outputs.patch-url }} | |
| GLPI_CACHE_KEY=${{ needs.prepare.outputs.cache-key }} | |
| GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} | |
| BUILDER_IMAGE=php:${{ needs.prepare.outputs.php-version }}-cli-alpine | |
| APP_IMAGE=php:${{ needs.prepare.outputs.php-version }}-apache | |
| cache-from: | | |
| type=gha,scope=${{ needs.prepare.outputs.cache-key }}-${{ matrix.platform.suffix }} | |
| type=gha,scope=main-${{ matrix.platform.suffix }} | |
| cache-to: "type=gha,mode=max,scope=${{ needs.prepare.outputs.cache-key }}-${{ matrix.platform.suffix }}" | |
| context: "glpi" | |
| labels: "${{ steps.meta.outputs.labels }}" | |
| platforms: "${{ matrix.platform.arch }}" | |
| pull: true | |
| sbom: ${{ needs.prepare.outputs.push == 'true' }} | |
| provenance: ${{ needs.prepare.outputs.push == 'true' && 'mode=max' || 'false' }} | |
| outputs: "type=image,\"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}\",push-by-digest=true,name-canonical=true,push=${{ needs.prepare.outputs.push }}" | |
| - name: "Export digest" | |
| if: ${{ needs.prepare.outputs.push == 'true' }} | |
| run: | | |
| mkdir -p ${{ runner.temp }}/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "${{ runner.temp }}/digests/${digest#sha256:}" | |
| - name: "Upload digest" | |
| if: ${{ needs.prepare.outputs.push == 'true' }} | |
| uses: "actions/upload-artifact@v4" | |
| with: | |
| name: "digests-${{ needs.prepare.outputs.artifact-prefix }}-${{ env.PLATFORM_PAIR }}" | |
| path: "${{ runner.temp }}/digests/*" | |
| if-no-files-found: "error" | |
| retention-days: 1 | |
| merge-manifests: | |
| name: "Merge manifests" | |
| runs-on: "ubuntu-latest" | |
| needs: [prepare, build] | |
| if: ${{ needs.prepare.outputs.push == 'true' }} | |
| steps: | |
| - name: "Download digests" | |
| uses: "actions/download-artifact@v4" | |
| with: | |
| path: "${{ runner.temp }}/digests" | |
| pattern: "digests-${{ needs.prepare.outputs.artifact-prefix }}-*" | |
| merge-multiple: true | |
| - name: "Verify digests" | |
| run: | | |
| mkdir -p "${{ runner.temp }}/digests" | |
| if [ -z "$(ls --almost-all ${{ runner.temp }}/digests)" ]; then | |
| echo "::error::No digest files found! Build job may have failed or artifacts expired." | |
| exit 1 | |
| fi | |
| - name: "Set up Docker Buildx" | |
| uses: "docker/setup-buildx-action@v3" | |
| - name: "Login to DockerHub" | |
| uses: "docker/login-action@v3" | |
| with: | |
| username: "${{ secrets.DOCKER_HUB_USERNAME }}" | |
| password: "${{ secrets.DOCKER_HUB_TOKEN }}" | |
| - name: "Login to Github container registry" | |
| uses: "docker/login-action@v3" | |
| with: | |
| registry: "ghcr.io" | |
| username: "${{ secrets.GHCR_USERNAME }}" | |
| password: "${{ secrets.GHCR_ACCESS_TOKEN }}" | |
| - name: "Docker metadata" | |
| id: "meta" | |
| uses: "docker/metadata-action@v5" | |
| with: | |
| images: | | |
| ${{ env.DOCKERHUB_IMAGE }} | |
| ${{ env.GHCR_IMAGE }} | |
| # Disable automatic latest tag - we control it via is-latest | |
| flavor: | | |
| latest=false | |
| tags: | | |
| # If image-tag is explicitly provided, use it directly | |
| type=raw,value=${{ needs.prepare.outputs.image-tag }},enable=${{ needs.prepare.outputs.image-tag != '' }} | |
| # Base version tag (always, when no explicit image-tag) | |
| type=raw,value=${{ needs.prepare.outputs.image-version }},enable=${{ needs.prepare.outputs.image-tag == '' }} | |
| # Only for releases | |
| type=semver,pattern={{major}},value=${{ needs.prepare.outputs.glpi-version }},enable=${{ needs.prepare.outputs.is-latest-major == 'true' }} | |
| type=semver,pattern={{major}}.{{minor}},value=${{ needs.prepare.outputs.glpi-version }},enable=${{ needs.prepare.outputs.is-latest-major == 'true' }} | |
| # Latest | |
| type=raw,value=latest,enable=${{ needs.prepare.outputs.is-latest == 'true' }} | |
| - name: "Create and push manifest" | |
| working-directory: "${{ runner.temp }}/digests" | |
| env: | |
| TAGS: ${{ steps.meta.outputs.tags }} | |
| run: | | |
| # Create manifest for DockerHub | |
| DOCKERHUB_TAGS=$(echo "$TAGS" | grep "^${{ env.DOCKERHUB_IMAGE }}" | xargs --replace={} echo "-t {}" | tr '\n' ' ') | |
| if [[ -n "$DOCKERHUB_TAGS" ]]; then | |
| echo "Creating DockerHub manifest..." | |
| docker buildx imagetools create $DOCKERHUB_TAGS \ | |
| $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) | |
| fi | |
| # Create manifest for GHCR | |
| GHCR_TAGS=$(echo "$TAGS" | grep "^${{ env.GHCR_IMAGE }}" | xargs --replace={} echo "-t {}" | tr '\n' ' ') | |
| if [[ -n "$GHCR_TAGS" ]]; then | |
| echo "Creating GHCR manifest..." | |
| docker buildx imagetools create $GHCR_TAGS \ | |
| $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) | |
| fi | |
| - name: "Inspect image" | |
| run: | | |
| # Get first tag for inspection | |
| docker buildx imagetools inspect ${{ fromJSON(steps.meta.outputs.json).tags[0] }} |