Skip to content

feat: implement comprehensive versioning strategy and CI cleanup #1

feat: implement comprehensive versioning strategy and CI cleanup

feat: implement comprehensive versioning strategy and CI cleanup #1

# .github/workflows/auto-tag-enhanced.yml
# Purpose: Enhanced auto-tagging with comprehensive outputs and error handling.
# Inputs:
# java-version: Java version (default: 21)
# artifact-name: Name of the built artifact (default: 'app')
# build-docker: Whether to build Docker image (default: true)
# run-tests: Whether to run tests before tagging (default: true)
# registry: Container registry URL (default: 'ghcr.io')
# notify-on-success: Send success notifications (default: false)
# notify-on-failure: Send failure notifications (default: true)
# create-changelog: Generate changelog (default: true)
# platforms: Docker platforms to build (default: 'linux/amd64,linux/arm64')
# Outputs:
# version: Project version
# tag-name: Created tag name
# release-created: Whether a new release was created
# release-url: URL of the created release
# docker-image: Docker image URL (if built)
# changelog: Generated changelog content
# artifacts-count: Number of artifacts uploaded
# Secrets:
# SLACK_WEBHOOK_URL: Optional Slack webhook for notifications
# GITHUB_TOKEN: Required for creating releases
# Usage:
# jobs:
# auto-tag:
# uses: org/workflows/.github/workflows/auto-tag-enhanced.yml@v1.0.0
# with:
# java-version: '17'
# artifact-name: 'my-app'
# build-docker: true
# run-tests: true
# registry: 'ghcr.io'
# notify-on-success: true
# create-changelog: true
# platforms: 'linux/amd64,linux/arm64'
# secrets:
# SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Versioning: Reference by tag for stability.
name: 🏷️ Enhanced Auto Tag & Release
on:
workflow_call:
inputs:
java-version:
required: false
type: string
default: '21'
artifact-name:
required: false
type: string
default: 'app'
build-docker:
required: false
type: boolean
default: true
run-tests:
required: false
type: boolean
default: true
registry:
required: false
type: string
default: 'ghcr.io'
notify-on-success:
required: false
type: boolean
default: false
notify-on-failure:
required: false
type: boolean
default: true
create-changelog:
required: false
type: boolean
default: true
platforms:
required: false
type: string
default: 'linux/amd64,linux/arm64'
outputs:
version:
description: "Project version"
value: ${{ jobs.auto-tag-and-release.outputs.version }}
tag-name:
description: "Created tag name"
value: ${{ jobs.auto-tag-and-release.outputs.tag-name }}
release-created:
description: "Whether a new release was created"
value: ${{ jobs.auto-tag-and-release.outputs.release-created }}
release-url:
description: "URL of the created release"
value: ${{ jobs.auto-tag-and-release.outputs.release-url }}
docker-image:
description: "Docker image URL (if built)"
value: ${{ jobs.publish-packages.outputs.docker-image }}
changelog:
description: "Generated changelog content"
value: ${{ jobs.auto-tag-and-release.outputs.changelog }}
artifacts-count:
description: "Number of artifacts uploaded"
value: ${{ jobs.auto-tag-and-release.outputs.artifacts-count }}
secrets:
SLACK_WEBHOOK_URL:
description: "Slack webhook URL for notifications"
required: false
GITHUB_TOKEN:

Check failure on line 109 in .github/workflows/auto-tag-enhanced.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/auto-tag-enhanced.yml

Invalid workflow file

secret name `GITHUB_TOKEN` within `workflow_call` can not be used since it would collide with system reserved name
description: "GitHub token for creating releases"
required: true
jobs:
validate-inputs:
name: πŸ” Validate Release Inputs
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
java-version-validated: ${{ steps.validate.outputs.java-version }}
platforms-validated: ${{ steps.validate.outputs.platforms }}
registry-validated: ${{ steps.validate.outputs.registry }}
steps:
- name: πŸ” Validate inputs
id: validate
run: |
JAVA_VERSION="${{ inputs.java-version }}"
PLATFORMS="${{ inputs.platforms }}"
REGISTRY="${{ inputs.registry }}"
ARTIFACT_NAME="${{ inputs.artifact-name }}"
# Validate Java version
if [[ ! "$JAVA_VERSION" =~ ^(8|11|17|21|22)$ ]]; then
echo "❌ Invalid Java version: $JAVA_VERSION"
exit 1
fi
# Validate platforms
IFS=',' read -ra PLATFORM_ARRAY <<< "$PLATFORMS"
for platform in "${PLATFORM_ARRAY[@]}"; do
platform=$(echo "$platform" | xargs)
if [[ ! "$platform" =~ ^linux/(amd64|arm64|arm/v7)$ ]]; then
echo "❌ Invalid platform: $platform"
exit 1
fi
done
# Validate registry format
if [[ ! "$REGISTRY" =~ ^[a-z0-9.-]+$ ]]; then
echo "❌ Invalid registry format: $REGISTRY"
exit 1
fi
# Validate artifact name
if [[ ! "$ARTIFACT_NAME" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "❌ Invalid artifact name: $ARTIFACT_NAME"
exit 1
fi
echo "java-version=$JAVA_VERSION" >> $GITHUB_OUTPUT
echo "platforms=$PLATFORMS" >> $GITHUB_OUTPUT
echo "registry=$REGISTRY" >> $GITHUB_OUTPUT
echo "βœ… Input validation completed"
auto-tag-and-release:
name: 🏷️ Create Tag & Release
runs-on: ubuntu-latest
needs: validate-inputs
permissions:
contents: write
packages: write
pull-requests: read
outputs:
version: ${{ steps.project-version.outputs.version }}
tag-name: ${{ steps.project-version.outputs.tag-name }}
release-created: ${{ steps.check-tag.outputs.exists == 'false' }}
release-url: ${{ steps.create-release.outputs.upload_url }}
changelog: ${{ steps.changelog.outputs.content }}
artifacts-count: ${{ steps.upload-artifacts.outputs.count }}
steps:
- name: πŸ“₯ Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: πŸ”§ Setup Java & Maven
uses: ./.github/actions/setup-java-maven
with:
java-version: ${{ needs.validate-inputs.outputs.java-version-validated }}
maven-opts: '-Xmx3g'
- name: πŸ§ͺ Run tests before tagging
if: inputs.run-tests
run: |
echo "πŸ§ͺ Running comprehensive tests..."
./mvnw clean verify -B -Dmaven.test.failure.ignore=false
echo "βœ… All tests passed"
- name: πŸ“„ Get project version from pom.xml
id: project-version
run: |
VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
TAG_NAME="v$VERSION"
# Validate version format
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then
echo "❌ Invalid version format: $VERSION"
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag-name=$TAG_NAME" >> $GITHUB_OUTPUT
echo "βœ… Project version: $VERSION"
echo "βœ… Tag name: $TAG_NAME"
- name: πŸ” Check if tag exists
id: check-tag
run: |
TAG_NAME="${{ steps.project-version.outputs.tag-name }}"
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "ℹ️ Tag $TAG_NAME already exists"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "βœ… Tag $TAG_NAME does not exist, proceeding with release"
fi
- name: πŸ“‹ Generate changelog
id: changelog
if: steps.check-tag.outputs.exists == 'false' && inputs.create-changelog
run: |
echo "πŸ“‹ Generating changelog..."
# Get the last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
CURRENT_TAG="${{ steps.project-version.outputs.tag-name }}"
if [ -z "$LAST_TAG" ]; then
echo "πŸ“ First release, generating initial changelog"
COMMITS=$(git log --oneline --pretty=format:"- %s (%h)" | head -20)
else
echo "πŸ“ Generating changelog from $LAST_TAG to $CURRENT_TAG"
COMMITS=$(git log --oneline --pretty=format:"- %s (%h)" "$LAST_TAG"..HEAD)
fi
# Create changelog content
cat > CHANGELOG.md << EOF
# πŸš€ Release $CURRENT_TAG
## πŸ“¦ What's New
$COMMITS
## πŸ”— Links
- **Full Changelog**: https://github.com/${{ github.repository }}/compare/$LAST_TAG...$CURRENT_TAG
- **Docker Image**: ${{ needs.validate-inputs.outputs.registry-validated }}/${{ github.repository }}/${{ inputs.artifact-name }}:$CURRENT_TAG
## πŸš€ Quick Start
### Using JAR
\`\`\`bash
java -jar ${{ inputs.artifact-name }}-${{ steps.project-version.outputs.version }}.jar
\`\`\`
### Using Docker
\`\`\`bash
docker run -p 8080:8080 ${{ needs.validate-inputs.outputs.registry-validated }}/${{ github.repository }}/${{ inputs.artifact-name }}:$CURRENT_TAG
\`\`\`
---
**πŸ€– This release was automatically created by GitHub Actions**
EOF
# Output changelog content
CHANGELOG_CONTENT=$(cat CHANGELOG.md)
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: πŸ“¦ Build application package
if: steps.check-tag.outputs.exists == 'false'
run: |
echo "πŸ—οΈ Building application package..."
./mvnw clean package -DskipTests -B -Dmaven.javadoc.skip=true
# Verify JAR was created
JAR_FILE="target/${{ inputs.artifact-name }}-${{ steps.project-version.outputs.version }}.jar"
if [ ! -f "$JAR_FILE" ]; then
echo "❌ JAR file not found: $JAR_FILE"
exit 1
fi
echo "βœ… Application package built successfully"
echo "πŸ“¦ JAR size: $(du -h "$JAR_FILE" | cut -f1)"
- name: 🏷️ Create and push tag
if: steps.check-tag.outputs.exists == 'false'
run: |
TAG_NAME="${{ steps.project-version.outputs.tag-name }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG_NAME" -m "Release $TAG_NAME"
git push origin "$TAG_NAME"
echo "βœ… Created and pushed tag: $TAG_NAME"
- name: πŸ“¦ Create GitHub Release
id: create-release
if: steps.check-tag.outputs.exists == 'false'
uses: softprops/action-gh-release@a74c6b72af54cfa997e81df42d94703d6313a2d0 # v2.0.6
with:
tag_name: ${{ steps.project-version.outputs.tag-name }}
name: πŸš€ Release ${{ steps.project-version.outputs.tag-name }}
body_path: CHANGELOG.md
draft: false
prerelease: ${{ contains(steps.project-version.outputs.version, 'SNAPSHOT') || contains(steps.project-version.outputs.version, 'RC') }}
files: |
target/${{ inputs.artifact-name }}-${{ steps.project-version.outputs.version }}.jar
target/${{ inputs.artifact-name }}-${{ steps.project-version.outputs.version }}-sources.jar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: πŸ“Š Count uploaded artifacts
id: upload-artifacts
if: steps.check-tag.outputs.exists == 'false'
run: |
COUNT=0
[ -f "target/${{ inputs.artifact-name }}-${{ steps.project-version.outputs.version }}.jar" ] && ((COUNT++))
[ -f "target/${{ inputs.artifact-name }}-${{ steps.project-version.outputs.version }}-sources.jar" ] && ((COUNT++))
echo "count=$COUNT" >> $GITHUB_OUTPUT
echo "πŸ“Š Uploaded $COUNT artifacts"
- name: ℹ️ Tag already exists
if: steps.check-tag.outputs.exists == 'true'
run: |
echo "ℹ️ Tag ${{ steps.project-version.outputs.tag-name }} already exists, skipping release creation"
publish-packages:
name: πŸ“¦ Publish Docker Packages
runs-on: ubuntu-latest
needs: [validate-inputs, auto-tag-and-release]
if: needs.auto-tag-and-release.outputs.release-created == 'true' && inputs.build-docker
permissions:
contents: read
packages: write
outputs:
docker-image: ${{ steps.build-push.outputs.image-url }}
image-digest: ${{ steps.build-push.outputs.image-digest }}
image-size: ${{ steps.build-push.outputs.image-size }}
steps:
- name: πŸ“₯ Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: πŸ”§ Setup Java & Maven
uses: ./.github/actions/setup-java-maven
with:
java-version: ${{ needs.validate-inputs.outputs.java-version-validated }}
- name: πŸ“¦ Build application for Docker
run: |
./mvnw clean package -DskipTests -B
echo "βœ… Application built for Docker"
- name: 🐳 Build and push Docker image
id: build-push
uses: ./.github/actions/docker-build-push
with:
image-name: ${{ inputs.artifact-name }}
image-tag: ${{ needs.auto-tag-and-release.outputs.tag-name }}
registry: ${{ needs.validate-inputs.outputs.registry-validated }}
push: 'true'
platforms: ${{ needs.validate-inputs.outputs.platforms-validated }}
build-args: |
VERSION=${{ needs.auto-tag-and-release.outputs.version }}
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=${{ github.sha }}
notify-success:
name: πŸ“’ Notify Success
runs-on: ubuntu-latest
needs: [auto-tag-and-release, publish-packages]
if: |
always() &&
needs.auto-tag-and-release.result == 'success' &&
inputs.notify-on-success &&
secrets.SLACK_WEBHOOK_URL != ''
permissions:
contents: read
steps:
- name: πŸ“’ Send success notification
uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 # v3.16.2
with:
status: success
webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
text: |
πŸŽ‰ Release Created Successfully!
πŸ“¦ **Release**: ${{ needs.auto-tag-and-release.outputs.tag-name }}
🏷️ **Version**: ${{ needs.auto-tag-and-release.outputs.version }}
πŸ”— **URL**: ${{ needs.auto-tag-and-release.outputs.release-url }}
πŸ“Š **Artifacts**: ${{ needs.auto-tag-and-release.outputs.artifacts-count }}
🐳 **Docker**: ${{ needs.publish-packages.outputs.docker-image || 'Not built' }}
Repository: ${{ github.repository }}
Actor: ${{ github.actor }}
notify-failure:
name: πŸ“’ Notify Failure
runs-on: ubuntu-latest
needs: [auto-tag-and-release, publish-packages]
if: |
always() &&
(needs.auto-tag-and-release.result == 'failure' || needs.publish-packages.result == 'failure') &&
inputs.notify-on-failure &&
secrets.SLACK_WEBHOOK_URL != ''
permissions:
contents: read
steps:
- name: πŸ“’ Send failure notification
uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 # v3.16.2
with:
status: failure
webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
text: |
🚨 Release Failed!
Repository: ${{ github.repository }}
Branch: ${{ github.ref_name }}
Actor: ${{ github.actor }}
πŸ”— **View Logs**: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}