Skip to content
Draft
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
306 changes: 306 additions & 0 deletions .github/workflows/create-release-tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
# Workflow to create a git tag when code is merged to main branch
# This tag can be used to version projects that reference this repository
#
# Usage: Projects can reference a specific version of common-github-actions using the tag:
# uses: chef/common-github-actions/.github/workflows/ci-main-pull-request.yml@v1.0.7
#
# Tag format: v{MAJOR}.{MINOR}.{PATCH}
# - MAJOR: Breaking changes
# - MINOR: New features, backward compatible
# - PATCH: Bug fixes, backward compatible
#
# This workflow can be:
# 1. Used directly in this repository (triggers on push/workflow_dispatch)
# 2. Called by other repositories via workflow_call

name: Create Release Tag on Merge

on:
push:
branches:
- main
workflow_dispatch:
inputs:
version_bump:
description: 'Version bump type'
required: true
default: 'patch'
type: choice
options:
- major
- minor
- patch
custom_version:
description: 'Custom version (overrides version_bump, format: X.Y.Z without v prefix)'
required: false
type: string
workflow_call:
inputs:
version_bump:
description: 'Version bump type (major, minor, patch)'
required: false
type: string
default: 'patch'
custom_version:
description: 'Custom version (overrides version_bump, format: X.Y.Z without v prefix)'
required: false
type: string
default: ''

permissions:
contents: write

env:
WORKFLOW_VERSION: '1.0.0'

jobs:
create-tag:
name: 'Create Release Tag'
runs-on: ubuntu-latest
outputs:
new_tag: ${{ steps.create_tag.outputs.new_tag }}
previous_tag: ${{ steps.get_latest_tag.outputs.latest_tag }}

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true

- name: Get latest tag
id: get_latest_tag
run: |
# Get the latest tag, default to v0.0.0 if no tags exist
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "latest_tag=${LATEST_TAG}" >> $GITHUB_OUTPUT
echo "Latest tag: ${LATEST_TAG}"

- name: Validate and parse version
id: calc_version
run: |
LATEST_TAG="${{ steps.get_latest_tag.outputs.latest_tag }}"

# Function to validate semver format (vX.Y.Z or X.Y.Z where X, Y, Z are integers)
validate_semver() {
local version="$1"
local stripped="${version#v}" # Remove 'v' prefix if present

# Check if it matches X.Y.Z where X, Y, Z are non-negative integers
if [[ "$stripped" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
echo "valid"
else
echo "invalid"
fi
}

# Function to extract semver components
extract_semver() {
local version="$1"
local stripped="${version#v}" # Remove 'v' prefix if present

# Extract just the X.Y.Z part, ignoring any suffix like -rc1, -beta, etc.
if [[ "$stripped" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
echo "${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}"
else
echo ""
fi
}

# Handle custom version input
if [ -n "${{ inputs.custom_version }}" ]; then
CUSTOM_VER="${{ inputs.custom_version }}"
CUSTOM_VER="${CUSTOM_VER#v}" # Strip 'v' prefix if user included it

if [[ "$(validate_semver "$CUSTOM_VER")" != "valid" ]]; then
echo "❌ Error: Custom version '$CUSTOM_VER' is not a valid semantic version."
echo " Expected format: X.Y.Z (e.g., 1.2.3)"
echo " Each component must be a non-negative integer."
exit 1
fi

echo "Using custom version: ${CUSTOM_VER}"
echo "new_version=${CUSTOM_VER}" >> $GITHUB_OUTPUT
exit 0
fi

# Validate latest tag format
EXTRACTED_VERSION=$(extract_semver "$LATEST_TAG")

if [ -z "$EXTRACTED_VERSION" ]; then
echo "⚠️ Warning: Latest tag '$LATEST_TAG' is not a valid semantic version."
echo " Expected format: vX.Y.Z (e.g., v1.2.3)"
echo " Starting from v0.0.0 instead."
EXTRACTED_VERSION="0.0.0"
elif [ "$EXTRACTED_VERSION" != "${LATEST_TAG#v}" ]; then
echo "⚠️ Warning: Latest tag '$LATEST_TAG' contains a suffix."
echo " Using base version: $EXTRACTED_VERSION"
fi

# Split version into parts
MAJOR=$(echo "$EXTRACTED_VERSION" | cut -d. -f1)
MINOR=$(echo "$EXTRACTED_VERSION" | cut -d. -f2)
PATCH=$(echo "$EXTRACTED_VERSION" | cut -d. -f3)

# Verify components are numeric (defensive check)
if ! [[ "$MAJOR" =~ ^[0-9]+$ ]] || ! [[ "$MINOR" =~ ^[0-9]+$ ]] || ! [[ "$PATCH" =~ ^[0-9]+$ ]]; then
echo "❌ Error: Version components must be numeric."
echo " Got: MAJOR=$MAJOR, MINOR=$MINOR, PATCH=$PATCH"
exit 1
fi

# Default to patch bump for push events, use input for workflow_dispatch
BUMP_TYPE="${{ inputs.version_bump }}"
if [ -z "$BUMP_TYPE" ]; then
BUMP_TYPE="patch"
fi

# Calculate new version based on bump type
case $BUMP_TYPE in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
*)
echo "❌ Error: Invalid bump type '$BUMP_TYPE'. Must be one of: major, minor, patch"
exit 1
;;
esac

NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT
echo "Bump type: ${BUMP_TYPE}"
echo "New version: ${NEW_VERSION}"

- name: Check if tag already exists
id: check_tag
run: |
NEW_TAG="v${{ steps.calc_version.outputs.new_version }}"
if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then
echo "Tag ${NEW_TAG} already exists!"
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "Tag ${NEW_TAG} does not exist, proceeding with creation"
echo "exists=false" >> $GITHUB_OUTPUT
fi

- name: Create and push tag
id: create_tag
if: steps.check_tag.outputs.exists == 'false'
run: |
NEW_TAG="v${{ steps.calc_version.outputs.new_version }}"

# Configure git
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# Create annotated tag with message
git tag -a "${NEW_TAG}" -m "Release ${NEW_TAG}

Automated release tag created on merge to main branch.

Previous version: ${{ steps.get_latest_tag.outputs.latest_tag }}
Commit: ${{ github.sha }}
Triggered by: ${{ github.actor }}"

# Push the tag
git push origin "${NEW_TAG}"

echo "new_tag=${NEW_TAG}" >> $GITHUB_OUTPUT
echo "✅ Created and pushed tag: ${NEW_TAG}"

- name: Skip tag creation (already exists)
if: steps.check_tag.outputs.exists == 'true'
run: |
echo "⚠️ Skipping tag creation - tag v${{ steps.calc_version.outputs.new_version }} already exists"

- name: Output summary
run: |
echo "## 🏷️ Release Tag Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Previous Tag | ${{ steps.get_latest_tag.outputs.latest_tag }} |" >> $GITHUB_STEP_SUMMARY
echo "| New Tag | v${{ steps.calc_version.outputs.new_version }} |" >> $GITHUB_STEP_SUMMARY
echo "| Commit SHA | ${{ github.sha }} |" >> $GITHUB_STEP_SUMMARY
echo "| Actor | ${{ github.actor }} |" >> $GITHUB_STEP_SUMMARY
echo "| Workflow Version | ${{ env.WORKFLOW_VERSION }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Usage" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Reference this version in your workflow:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`yaml" >> $GITHUB_STEP_SUMMARY
echo "uses: chef/common-github-actions/.github/workflows/ci-main-pull-request.yml@v${{ steps.calc_version.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY

create-release:
name: 'Create GitHub Release'
runs-on: ubuntu-latest
needs: create-tag
if: needs.create-tag.outputs.new_tag != ''

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ needs.create-tag.outputs.new_tag }}

- name: Generate release notes
id: release_notes
run: |
PREVIOUS_TAG="${{ needs.create-tag.outputs.previous_tag }}"
NEW_TAG="${{ needs.create-tag.outputs.new_tag }}"

# Generate changelog between tags
if [ "$PREVIOUS_TAG" != "v0.0.0" ]; then
CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${PREVIOUS_TAG}..HEAD 2>/dev/null || echo "- Initial release")
else
CHANGELOG="- Initial release"
fi

# Write to file for multi-line handling
cat > release_notes.md << EOF
## What's Changed

${CHANGELOG}

## Usage

Reference this version in your workflow:

\`\`\`yaml
jobs:
call-ci-main-pr-check-pipeline:
uses: chef/common-github-actions/.github/workflows/ci-main-pull-request.yml@${NEW_TAG}
secrets: inherit
permissions:
id-token: write
contents: read
\`\`\`

## Full Changelog

https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${NEW_TAG}
EOF

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.create-tag.outputs.new_tag }}
name: "Release ${{ needs.create-tag.outputs.new_tag }}"
body_path: release_notes.md
draft: false
prerelease: false
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 changes: 3 additions & 12 deletions .github/workflows/stubs/ci-main-pull-request-stub.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,12 @@ on:

permissions:
contents: read

env:
STUB_VERSION: "1.0.7"

jobs:
echo_version:
name: 'Echo stub version'
runs-on: ubuntu-latest
steps:
- name: echo version of stub and inputs
run: |
echo "CI main pull request stub version $STUB_VERSION"

call-ci-main-pr-check-pipeline:
uses: chef/common-github-actions/.github/workflows/ci-main-pull-request.yml@main
# To pin to a specific version, change @main to a tag like @v1.0.7
# Available tags: https://github.com/chef/common-github-actions/tags
uses: chef/common-github-actions/.github/workflows/ci-main-pull-request.yml@main # or use @v1.0.7
secrets: inherit
permissions:
id-token: write
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/stubs/create-release-tag-stub.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Stub to call the common GitHub Action for creating release tags
# Copy this file to your repository's .github/workflows/ directory
#
# This workflow will create a git tag when code is merged to main branch
# Projects can then reference a specific version of common-github-actions

name: Create Release Tag

on:
push:
branches:
- main
workflow_dispatch:
inputs:
version_bump:
description: 'Version bump type'
required: true
default: 'patch'
type: choice
options:
- major
- minor
- patch
custom_version:
description: 'Custom version (overrides version_bump, format: X.Y.Z without v prefix)'
required: false
type: string

permissions:
contents: write

jobs:
call-create-release-tag:
# To pin to a specific version, change @main to a tag like @v1.0.7
# Available tags: https://github.com/chef/common-github-actions/tags
uses: chef/common-github-actions/.github/workflows/create-release-tag.yml@main
secrets: inherit
permissions:
contents: write
with:
version_bump: ${{ inputs.version_bump || 'patch' }}
custom_version: ${{ inputs.custom_version || '' }}
Loading