mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 14:28:01 -05:00
ci: add tag-based multi-arch Docker publish to GHCR and Docker Hub
CHANGES - Add GitHub Actions workflow to publish Docker images on tags - Build multi-arch images with Buildx and QEMU across amd64, arm64 - Tag images using semver; push to GHCR and Docker Hub - Set :latest only for highest semver tag via imagetools - Gate patterns workflow steps on detected changes instead of failing - Auto-detect GitHub owner and repo from git remote URL - Remove hardcoded repository values in changelog release manager - Normalize image names to lowercase for registry compatibility - Enable GitHub Actions cache for faster Docker builds - Add VS Code dictionary entries for Docker-related terms
This commit is contained in:
149
.github/workflows/docker-publish-on-tag.yml
vendored
Normal file
149
.github/workflows/docker-publish-on-tag.yml
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
name: Release Docker image on tag (GHCR + Docker Hub)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"] # e.g., v1.4.300
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # needed for GHCR with GITHUB_TOKEN
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
# Optional safety: only run from your fork
|
||||
if: ${{ github.repository_owner == 'ksylvan' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
is_latest: ${{ steps.latest.outputs.is_latest }}
|
||||
owner_lc: ${{ steps.vars.outputs.owner_lc }}
|
||||
repo_lc: ${{ steps.vars.outputs.repo_lc }}
|
||||
dockerhub_user_lc: ${{ steps.dh.outputs.user_lc }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # full history for tag comparisons
|
||||
|
||||
- name: Fetch all tags
|
||||
run: git fetch --tags --force
|
||||
|
||||
# More reliable cross-builds
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Compute lowercase owner/repo for registry image names
|
||||
- name: Compute image names
|
||||
id: vars
|
||||
run: |
|
||||
OWNER="${GITHUB_REPOSITORY_OWNER}"
|
||||
REPO="${GITHUB_REPOSITORY#*/}"
|
||||
echo "owner_lc=${OWNER,,}" >> "$GITHUB_OUTPUT"
|
||||
echo "repo_lc=${REPO,,}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Lowercase Docker Hub username (belt & suspenders)
|
||||
- name: Lowercase Docker Hub username
|
||||
id: dh
|
||||
run: echo "user_lc=${DOCKERHUB_USERNAME,,}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
|
||||
# Determine if the current tag is the highest vX.Y.Z (no pre-releases)
|
||||
- name: Is this the latest semver tag?
|
||||
id: latest
|
||||
shell: bash
|
||||
run: |
|
||||
CTAG="${GITHUB_REF_NAME}"
|
||||
LATEST="$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n1)"
|
||||
echo "current_tag=$CTAG" >> "$GITHUB_OUTPUT"
|
||||
echo "latest_tag=$LATEST" >> "$GITHUB_OUTPUT"
|
||||
if [[ "$CTAG" == "$LATEST" ]]; then
|
||||
echo "is_latest=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_latest=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Login to GHCR (uses built-in GITHUB_TOKEN)
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Login to Docker Hub
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ steps.dh.outputs.user_lc }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Generate versioned tags/labels for BOTH registries (no :latest here)
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ steps.vars.outputs.owner_lc }}/${{ steps.vars.outputs.repo_lc }}
|
||||
docker.io/${{ steps.dh.outputs.user_lc }}/${{ steps.vars.outputs.repo_lc }}
|
||||
tags: |
|
||||
type=ref,event=tag # v1.4.300
|
||||
type=semver,pattern={{version}} # 1.4.300 (optional)
|
||||
type=semver,pattern={{major}}.{{minor}} # 1.4 (optional)
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
|
||||
|
||||
- name: Build and push (multi-arch)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
# Separate job to (re)point :latest — serialized to avoid races
|
||||
move-latest:
|
||||
needs: build-and-push
|
||||
if: ${{ needs.build-and-push.outputs.is_latest == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Only one "latest" move at a time; newer runs cancel older in-progress ones
|
||||
concurrency:
|
||||
group: latest-${{ github.repository }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Tag :latest on GHCR
|
||||
run: |
|
||||
SRC="ghcr.io/${{ needs.build-and-push.outputs.owner_lc }}/${{ needs.build-and-push.outputs.repo_lc }}:${{ github.ref_name }}"
|
||||
DST="ghcr.io/${{ needs.build-and-push.outputs.owner_lc }}/${{ needs.build-and-push.outputs.repo_lc }}:latest"
|
||||
docker buildx imagetools create -t "$DST" "$SRC"
|
||||
|
||||
- name: Tag :latest on Docker Hub
|
||||
run: |
|
||||
SRC="docker.io/${{ needs.build-and-push.outputs.dockerhub_user_lc }}/${{ needs.build-and-push.outputs.repo_lc }}:${{ github.ref_name }}"
|
||||
DST="docker.io/${{ needs.build-and-push.outputs.dockerhub_user_lc }}/${{ needs.build-and-push.outputs.repo_lc }}:latest"
|
||||
docker buildx imagetools create -t "$DST" "$SRC"
|
||||
8
.github/workflows/patterns.yaml
vendored
8
.github/workflows/patterns.yaml
vendored
@@ -16,17 +16,23 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Verify Changes in Patterns Folder
|
||||
id: check-changes
|
||||
run: |
|
||||
git fetch origin
|
||||
if git diff --quiet HEAD~1 -- data/patterns; then
|
||||
echo "No changes detected in patterns folder."
|
||||
exit 1
|
||||
echo "changes=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Changes detected in patterns folder."
|
||||
echo "changes=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Zip the Patterns Folder
|
||||
if: steps.check-changes.outputs.changes == 'true'
|
||||
run: zip -r patterns.zip data/patterns/
|
||||
|
||||
- name: Upload Patterns Artifact
|
||||
if: steps.check-changes.outputs.changes == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: patterns
|
||||
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -13,6 +13,7 @@
|
||||
"Behrens",
|
||||
"blindspots",
|
||||
"Bombal",
|
||||
"Buildx",
|
||||
"Callirhoe",
|
||||
"Callirrhoe",
|
||||
"Cerebras",
|
||||
@@ -31,6 +32,7 @@
|
||||
"Despina",
|
||||
"direnv",
|
||||
"DMARC",
|
||||
"DOCKERHUB",
|
||||
"dryrun",
|
||||
"dsrp",
|
||||
"editability",
|
||||
@@ -73,6 +75,7 @@
|
||||
"Hormozi's",
|
||||
"horts",
|
||||
"HTMLURL",
|
||||
"imagetools",
|
||||
"jaredmontoya",
|
||||
"jessevdk",
|
||||
"Jina",
|
||||
@@ -108,6 +111,7 @@
|
||||
"ollamaapi",
|
||||
"openaiapi",
|
||||
"opencode",
|
||||
"opencontainers",
|
||||
"openrouter",
|
||||
"Orus",
|
||||
"osascript",
|
||||
|
||||
7
cmd/generate_changelog/incoming/1732.txt
Normal file
7
cmd/generate_changelog/incoming/1732.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
### PR [#1732](https://github.com/danielmiessler/Fabric/pull/1732) by [ksylvan](https://github.com/ksylvan): CI Infra: Changelog Generation Tool + Docker Image Pubishing
|
||||
|
||||
- Add GitHub Actions workflow to publish Docker images on tags
|
||||
- Build multi-arch images with Buildx and QEMU across amd64, arm64
|
||||
- Tag images using semver; push to GHCR and Docker Hub
|
||||
- Gate patterns workflow steps on detected changes instead of failing
|
||||
- Auto-detect GitHub owner and repo from git remote URL
|
||||
@@ -3,6 +3,9 @@ package internal
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/cache"
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/config"
|
||||
@@ -17,17 +20,50 @@ type ReleaseManager struct {
|
||||
repo string
|
||||
}
|
||||
|
||||
// getGitHubInfo extracts owner and repo from git remote origin URL
|
||||
func getGitHubInfo() (owner, repo string, err error) {
|
||||
cmd := exec.Command("git", "remote", "get-url", "origin")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get git remote URL: %w", err)
|
||||
}
|
||||
|
||||
url := strings.TrimSpace(string(output))
|
||||
|
||||
// Handle both SSH and HTTPS URLs
|
||||
// SSH: git@github.com:owner/repo.git
|
||||
// HTTPS: https://github.com/owner/repo.git
|
||||
var re *regexp.Regexp
|
||||
if strings.HasPrefix(url, "git@") {
|
||||
re = regexp.MustCompile(`git@github\.com:([^/]+)/([^/.]+)(?:\.git)?`)
|
||||
} else {
|
||||
re = regexp.MustCompile(`https://github\.com/([^/]+)/([^/.]+)(?:\.git)?`)
|
||||
}
|
||||
|
||||
matches := re.FindStringSubmatch(url)
|
||||
if len(matches) < 3 {
|
||||
return "", "", fmt.Errorf("invalid GitHub URL format: %s", url)
|
||||
}
|
||||
|
||||
return matches[1], matches[2], nil
|
||||
}
|
||||
|
||||
func NewReleaseManager(cfg *config.Config) (*ReleaseManager, error) {
|
||||
cache, err := cache.New(cfg.CacheFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create cache: %w", err)
|
||||
}
|
||||
|
||||
owner, repo, err := getGitHubInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get GitHub repository info: %w", err)
|
||||
}
|
||||
|
||||
return &ReleaseManager{
|
||||
cache: cache,
|
||||
githubToken: cfg.GitHubToken,
|
||||
owner: "danielmiessler",
|
||||
repo: "fabric",
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user