feat: Add push_dev option for development image in release workflow and update tag handling
This commit is contained in:
@@ -15,6 +15,14 @@ on:
|
|||||||
- patch
|
- patch
|
||||||
- minor
|
- minor
|
||||||
- development
|
- development
|
||||||
|
push_dev:
|
||||||
|
description: "If true, push the :dev image to GHCR for development releases"
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- "true"
|
||||||
|
- "false"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -39,61 +47,66 @@ jobs:
|
|||||||
EVENT_NAME: ${{ github.event_name }}
|
EVENT_NAME: ${{ github.event_name }}
|
||||||
REF_NAME: ${{ github.ref_name }}
|
REF_NAME: ${{ github.ref_name }}
|
||||||
BUMP_TYPE: ${{ github.event.inputs.bump || 'patch' }}
|
BUMP_TYPE: ${{ github.event.inputs.bump || 'patch' }}
|
||||||
|
PUSH_DEV: ${{ github.event.inputs.push_dev || 'false' }}
|
||||||
run: |
|
run: |
|
||||||
if [ "$EVENT_NAME" = "push" ] && [ -n "$REF_NAME" ]; then
|
if [ "$EVENT_NAME" = "push" ] && [ -n "$REF_NAME" ]; then
|
||||||
TAG="$REF_NAME"
|
TAG="$REF_NAME"
|
||||||
else
|
else
|
||||||
TAG="$(python3 - <<'PY'
|
TAG="$(python3 - <<'PY'
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
repo = os.environ["REPO"]
|
repo = os.environ["REPO"]
|
||||||
token = os.environ.get("GH_TOKEN", "")
|
token = os.environ.get("GH_TOKEN", "")
|
||||||
bump = os.environ.get("BUMP_TYPE", "patch").strip().lower()
|
bump = os.environ.get("BUMP_TYPE", "patch").strip().lower()
|
||||||
|
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
f"https://api.github.com/repos/{repo}/releases/latest",
|
f"https://api.github.com/repos/{repo}/releases/latest",
|
||||||
headers={
|
headers={
|
||||||
"Authorization": f"Bearer {token}",
|
"Authorization": f"Bearer {token}",
|
||||||
"Accept": "application/vnd.github+json",
|
"Accept": "application/vnd.github+json",
|
||||||
"User-Agent": "legendary-octo-garbanzo-release-workflow",
|
"User-Agent": "legendary-octo-garbanzo-release-workflow",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
latest_tag = "v3.0.0"
|
latest_tag = "v3.0.0"
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=20) as resp:
|
with urllib.request.urlopen(req, timeout=20) as resp:
|
||||||
data = json.loads(resp.read().decode("utf-8"))
|
data = json.loads(resp.read().decode("utf-8"))
|
||||||
latest_tag = (data.get("tag_name") or latest_tag).strip()
|
latest_tag = (data.get("tag_name") or latest_tag).strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
m = re.match(r"^v(\d+)\.(\d+)\.(\d+)$", latest_tag)
|
m = re.match(r"^v(\d+)\.(\d+)\.(\d+)$", latest_tag)
|
||||||
if not m:
|
if not m:
|
||||||
major, minor, patch = 3, 0, 0
|
major, minor, patch = 3, 0, 0
|
||||||
else:
|
else:
|
||||||
major, minor, patch = map(int, m.groups())
|
major, minor, patch = map(int, m.groups())
|
||||||
|
|
||||||
# Bump strategy: major / minor / patch
|
# Bump strategy: major / minor / patch
|
||||||
if bump == "major":
|
if bump == "major":
|
||||||
major += 1
|
major += 1
|
||||||
minor = 0
|
minor = 0
|
||||||
patch = 0
|
patch = 0
|
||||||
elif bump == "minor":
|
elif bump == "minor":
|
||||||
minor += 1
|
minor += 1
|
||||||
patch = 0
|
patch = 0
|
||||||
else:
|
else:
|
||||||
patch += 1
|
patch += 1
|
||||||
|
|
||||||
print(f"v{major}.{minor}.{patch}")
|
if bump == "development":
|
||||||
PY
|
# use dev suffix for development releases
|
||||||
)"
|
print(f"v{major}.{minor}.{patch}-dev")
|
||||||
|
else:
|
||||||
|
print(f"v{major}.{minor}.{patch}")
|
||||||
|
PY
|
||||||
|
)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+(-dev(\.[0-9]+)?)?$'; then
|
||||||
echo "Error: tag '$TAG' is not valid semver (vX.Y.Z)"
|
echo "Error: tag '$TAG' is not valid semver (vX.Y.Z or vX.Y.Z-dev)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -116,12 +129,15 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; do
|
# Ensure tag uniqueness: if tag exists append numeric suffix
|
||||||
BASE="${TAG%.*}"
|
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
|
||||||
PATCH="${TAG##*.}"
|
i=1
|
||||||
PATCH="$((PATCH + 1))"
|
base="$TAG"
|
||||||
TAG="$BASE.$PATCH"
|
while git rev-parse -q --verify "refs/tags/${base}.${i}" >/dev/null; do
|
||||||
done
|
i="$((i + 1))"
|
||||||
|
done
|
||||||
|
TAG="${base}.${i}"
|
||||||
|
fi
|
||||||
|
|
||||||
IMAGE="ghcr.io/aiirondev/legendary-octo-garbanzo:${TAG}"
|
IMAGE="ghcr.io/aiirondev/legendary-octo-garbanzo:${TAG}"
|
||||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||||
@@ -131,9 +147,14 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "is_development=false" >> "$GITHUB_OUTPUT"
|
echo "is_development=false" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
if [ "${BUMP_TYPE:-}" = "development" ] && [ "${PUSH_DEV:-}" = "true" ]; then
|
||||||
|
echo "push_dev=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "push_dev=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Create and push tag for manual releases
|
- name: Create and push tag for manual releases
|
||||||
if: github.event_name == 'workflow_dispatch' && steps.meta.outputs.is_development != 'true'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
@@ -162,14 +183,15 @@ jobs:
|
|||||||
${{ steps.meta.outputs.image }}
|
${{ steps.meta.outputs.image }}
|
||||||
ghcr.io/aiirondev/legendary-octo-garbanzo:latest
|
ghcr.io/aiirondev/legendary-octo-garbanzo:latest
|
||||||
|
|
||||||
- name: Build and push development image
|
- name: Build and push development image (:dev)
|
||||||
|
if: steps.meta.outputs.is_development == 'true' && steps.meta.outputs.push_dev == 'true'
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/aiirondev/legendary-octo-garbanzo:development
|
ghcr.io/aiirondev/legendary-octo-garbanzo:dev
|
||||||
|
|
||||||
- name: Build local image tar for offline deploy
|
- name: Build local image tar for offline deploy
|
||||||
run: |
|
run: |
|
||||||
@@ -196,23 +218,23 @@ jobs:
|
|||||||
- name: Build local development image tar for offline deploy
|
- name: Build local development image tar for offline deploy
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
DEV_IMG=ghcr.io/aiirondev/legendary-octo-garbanzo:development
|
DEV_IMG=ghcr.io/aiirondev/legendary-octo-garbanzo:dev
|
||||||
|
|
||||||
if docker image inspect "$DEV_IMG" >/dev/null 2>&1; then
|
if docker image inspect "$DEV_IMG" >/dev/null 2>&1; then
|
||||||
echo "Using local development image $DEV_IMG"
|
echo "Using local development image $DEV_IMG"
|
||||||
docker save "$DEV_IMG" | gzip > "inventarsystem-image-development.tar.gz"
|
docker save "$DEV_IMG" | gzip > "inventarsystem-image-dev.tar.gz"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Local development image not found, trying to pull $DEV_IMG"
|
echo "Local development image not found, trying to pull $DEV_IMG"
|
||||||
if docker pull "$DEV_IMG" >/dev/null 2>&1; then
|
if docker pull "$DEV_IMG" >/dev/null 2>&1; then
|
||||||
docker save "$DEV_IMG" | gzip > "inventarsystem-image-development.tar.gz"
|
docker save "$DEV_IMG" | gzip > "inventarsystem-image-dev.tar.gz"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Pull failed, attempting local docker build for development image"
|
echo "Pull failed, attempting local docker build for development image"
|
||||||
docker build -t "$DEV_IMG" .
|
docker build -t "$DEV_IMG" .
|
||||||
docker save "$DEV_IMG" | gzip > "inventarsystem-image-development.tar.gz"
|
docker save "$DEV_IMG" | gzip > "inventarsystem-image-dev.tar.gz"
|
||||||
|
|
||||||
- name: Create release-only docker bundle
|
- name: Create release-only docker bundle
|
||||||
run: |
|
run: |
|
||||||
@@ -298,16 +320,16 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Include optional development image tar and helper note
|
# Include optional development image tar and helper note
|
||||||
if [ -f "inventarsystem-image-development.tar.gz" ]; then
|
if [ -f "inventarsystem-image-dev.tar.gz" ]; then
|
||||||
cp inventarsystem-image-development.tar.gz release-bundle/ || true
|
cp inventarsystem-image-dev.tar.gz release-bundle/ || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > release-bundle/DEVELOPMENT.md <<'EOF'
|
cat > release-bundle/DEVELOPMENT.md <<'EOF'
|
||||||
This bundle contains an optional development image tar: inventarsystem-image-development.tar.gz
|
This bundle contains an optional development image tar: inventarsystem-image-dev.tar.gz
|
||||||
|
|
||||||
To install the development build on a target host, extract the bundle and run:
|
To install the development build on a target host, extract the bundle and run:
|
||||||
|
|
||||||
./update.sh development
|
./update.sh dev
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -316,13 +338,13 @@ jobs:
|
|||||||
tar -czf inventarsystem-docker-bundle.tar.gz -C release-bundle .
|
tar -czf inventarsystem-docker-bundle.tar.gz -C release-bundle .
|
||||||
|
|
||||||
- name: Create or update GitHub Release
|
- name: Create or update GitHub Release
|
||||||
if: steps.meta.outputs.is_development != 'true'
|
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ steps.meta.outputs.tag }}
|
tag_name: ${{ steps.meta.outputs.tag }}
|
||||||
|
prerelease: ${{ steps.meta.outputs.is_development }}
|
||||||
files: |
|
files: |
|
||||||
inventarsystem-docker-bundle.tar.gz
|
inventarsystem-docker-bundle.tar.gz
|
||||||
inventarsystem-image-${{ steps.meta.outputs.tag }}.tar.gz
|
inventarsystem-image-${{ steps.meta.outputs.tag }}.tar.gz
|
||||||
inventarsystem-image-development.tar.gz
|
inventarsystem-image-dev.tar.gz
|
||||||
fail_on_unmatched_files: false
|
fail_on_unmatched_files: false
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ main() {
|
|||||||
# If user requested a development install, perform a simple dev deploy flow
|
# If user requested a development install, perform a simple dev deploy flow
|
||||||
if [ "$MODE" = "development" ]; then
|
if [ "$MODE" = "development" ]; then
|
||||||
log_message "Requested development install"
|
log_message "Requested development install"
|
||||||
local tag="development"
|
local tag="dev"
|
||||||
local app_image="$APP_IMAGE_REPO:$tag"
|
local app_image="$APP_IMAGE_REPO:$tag"
|
||||||
local compose_path="$PROJECT_DIR/$COMPOSE_FILE"
|
local compose_path="$PROJECT_DIR/$COMPOSE_FILE"
|
||||||
|
|
||||||
@@ -523,7 +523,6 @@ EOF
|
|||||||
# Bring up stack
|
# Bring up stack
|
||||||
docker compose -f "$compose_path" --env-file "$ENV_FILE" pull app mongodb >> "$LOG_FILE" 2>&1 || true
|
docker compose -f "$compose_path" --env-file "$ENV_FILE" pull app mongodb >> "$LOG_FILE" 2>&1 || true
|
||||||
docker compose -f "$compose_path" --env-file "$ENV_FILE" up -d --remove-orphans >> "$LOG_FILE" 2>&1
|
docker compose -f "$compose_path" --env-file "$ENV_FILE" up -d --remove-orphans >> "$LOG_FILE" 2>&1
|
||||||
docker tag "$app_image" "$APP_IMAGE_REPO:latest" >> "$LOG_FILE" 2>&1 || true
|
|
||||||
|
|
||||||
if ! verify_stack_health; then
|
if ! verify_stack_health; then
|
||||||
log_message "ERROR: Development deployment failed health check"
|
log_message "ERROR: Development deployment failed health check"
|
||||||
|
|||||||
Reference in New Issue
Block a user