Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Git
.git
.gitignore
.gitattributes

# Build artifacts
dist/
**/dist/
# Allow CLI binaries for Docker builds
!dist/github-actions-utils-cli-linux-*
build/
**/build/
*.swp
*.swo
*~

# Node
node_modules/
**/node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm
.pnpm-store

# macOS files
**/.DS_Store

# Testing
coverage/
.nyc_output

# IDE
.idea/
.vscode/
*.iml
*.swp
*.swo
*~
.DS_Store

# OS
Thumbs.db

# Deployment
deploy/
.pulumi/

# Docs
docs/
*.md
!README.md

# Examples and tests
examples/
*_test.go
testdata/

# CI/CD
.github/
.gitlab-ci.yml
.travis.yml

# Misc
.env
.env.*
!.env.example
tmp/
temp/
*.log
197 changes: 197 additions & 0 deletions .github/workflows/build-cli-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
name: Build CLI Docker Images

on:
workflow_call:
inputs:
tag_name:
description: "Tag name for Docker images"
required: true
type: string
is_prerelease:
description: "Whether this is a prerelease"
required: true
type: string

jobs:
build:
name: Build CLI Docker Image (${{ matrix.platform.name }})
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
platform:
- name: linux/amd64
tag: linux-amd64
- name: linux/arm64
tag: linux-arm64
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.platform.name }}
cancel-in-progress: true

steps:
- name: Checkout Repository
uses: actions/checkout@v5

- name: Download CLI artifacts
uses: actions/download-artifact@v6
with:
path: dist
pattern: cli-${{ matrix.platform.tag }}
merge-multiple: true

- name: List downloaded files
run: ls -lh dist/

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
docker.io/${{ github.repository }}
ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ inputs.tag_name }}
labels: |
org.opencontainers.image.title=GitHub Actions Utils CLI
org.opencontainers.image.description=MCP server for GitHub Actions utilities
maintainer=techprimate GmbH <opensource@techprimate.com>

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build ${{ github.event_name == 'pull_request' && '(dry run)' || 'and push' }} by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: ${{ matrix.platform.name }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache-${{ matrix.platform.tag }}
cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache-${{ matrix.platform.tag }},mode=max
outputs: type=image,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index

- name: Export digest
if: github.event_name != 'pull_request'
run: |
set -e
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
ls -la /tmp/digests

- name: Upload digest
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v5
with:
name: cli-docker-digests-${{ matrix.platform.tag }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

merge:
name: Create Manifest List
runs-on: ubuntu-latest
needs: build
if: github.event_name != 'pull_request'
permissions:
contents: read
packages: write
id-token: write
timeout-minutes: 10
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-merge
cancel-in-progress: true
steps:
- name: Download digests
uses: actions/download-artifact@v6
with:
path: /tmp/digests
pattern: cli-docker-digests-*
merge-multiple: true

- name: List digests
run: ls -la /tmp/digests

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Create Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
docker.io/${{ github.repository }}
ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ inputs.tag_name }}
type=raw,value=latest,enable=${{ inputs.is_prerelease == 'true' }}
type=semver,pattern={{version}},value=${{ inputs.tag_name }},enable=${{ inputs.is_prerelease == 'false' }}
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.tag_name }},enable=${{ inputs.is_prerelease == 'false' }}
type=semver,pattern={{major}},value=${{ inputs.tag_name }},enable=${{ inputs.is_prerelease == 'false' }}

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create manifest list and push
working-directory: /tmp/digests
run: |
# Extract all tags
image_tags=$(printf '%s' "$DOCKER_METADATA_OUTPUT_JSON" | jq -cr '.tags | map("-t " + .) | join(" ")')
echo "Creating manifest list with tags: $image_tags"

# Extract unique image names (without tags) to build digest references
image_names=$(printf '%s' "$DOCKER_METADATA_OUTPUT_JSON" | jq -cr '.tags | map(split(":")[0]) | unique | join(" ")')
echo "Image names: $image_names"

# Build digest references for all images
digest_refs=""
for image_name in $image_names; do
for digest_file in *; do
digest_refs="$digest_refs ${image_name}@sha256:${digest_file}"
done
done
echo "Digest references: $digest_refs"

echo "Creating manifest using buildx..."
docker buildx imagetools create $image_tags $digest_refs

# Inspect the first tag for verification
first_tag=$(printf '%s' "$DOCKER_METADATA_OUTPUT_JSON" | jq -cr '.tags[0]')
docker buildx imagetools inspect "$first_tag"
56 changes: 47 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- "v*.*.*"
branches:
- main
pull_request:
workflow_dispatch:

permissions:
Expand All @@ -28,8 +29,16 @@ jobs:
id: metadata
shell: bash
run: |
# Check if this is a pull request (dry run)
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "πŸ” Pull Request detected - running in DRY RUN mode"
COMMIT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
VERSION="pr-${{ github.event.pull_request.number }}-${COMMIT_SHA}"
IS_PRERELEASE="true"
RELEASE_NAME="PR #${{ github.event.pull_request.number }} Test Build"
TAG_NAME="pr-${{ github.event.pull_request.number }}"
# Check if this is a tag push (release) or branch push (pre-release)
if echo "${{ github.ref }}" | grep -q "^refs/tags/v"; then
elif echo "${{ github.ref }}" | grep -q "^refs/tags/v"; then
# Stable release from tag
VERSION=$(echo "${{ github.ref }}" | sed 's|refs/tags/v||')
IS_PRERELEASE="false"
Expand Down Expand Up @@ -58,22 +67,33 @@ jobs:
echo "Release Name: $RELEASE_NAME"
echo "Tag Name: $TAG_NAME"

build:
name: Build Binaries
build-cli-binaries:
name: Build CLI Binaries
needs: prepare
uses: ./.github/workflows/build-binaries.yml
with:
version: ${{ needs.prepare.outputs.version }}
commit_sha: ${{ needs.prepare.outputs.commit_sha }}
build_date: ${{ needs.prepare.outputs.build_date }}
secrets:
DEVELOPER_ID_P12_BASE64: ${{ secrets.DEVELOPER_ID_P12_BASE64 }}
DEVELOPER_ID_PASSWORD: ${{ secrets.DEVELOPER_ID_PASSWORD }}
APPLE_NOTARIZATION_APPLE_ID_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_APPLE_ID_PASSWORD }}
secrets: inherit

build-cli-docker:
name: Build CLI Docker Images
needs: [prepare, build-cli-binaries]
permissions:
contents: read
packages: write
attestations: write
id-token: write
uses: ./.github/workflows/build-cli-docker.yml
with:
tag_name: ${{ needs.prepare.outputs.tag_name }}
is_prerelease: ${{ needs.prepare.outputs.is_prerelease }}
secrets: inherit

release:
name: Create Release
needs: [prepare, build]
needs: [prepare, build-cli-binaries, build-cli-docker]
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand Down Expand Up @@ -113,7 +133,7 @@ jobs:
cat dist/release-notes.md

- name: Delete existing latest release (pre-release only)
if: needs.prepare.outputs.is_prerelease == 'true'
if: needs.prepare.outputs.is_prerelease == 'true' && github.event_name != 'pull_request'
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -122,6 +142,7 @@ jobs:
gh release delete latest --yes --cleanup-tag || true

- name: Create Release
if: github.event_name != 'pull_request'
uses: softprops/action-gh-release@v2
with:
name: ${{ needs.prepare.outputs.release_name }}
Expand All @@ -136,3 +157,20 @@ jobs:
dist/checksums.txt
draft: false
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}

release-required-check:
name: Release - Required Check
needs:
- prepare
- build-cli-binaries
- build-cli-docker
- release
if: always()
runs-on: ubuntu-latest
steps:
# If any jobs we depend on fail, get cancelled, or time out, this job will fail.
# Skipped jobs are not considered failures.
- name: Check for failures
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
echo "One of the release jobs has failed." && exit 1
Loading