Skip to content

Commit 10a8e36

Browse files
prestoncabeclaude
andcommitted
library-api: add deployment workflow and version management
Implement semantic versioning workflow with automated Cloud Run deployment: **Deployment infrastructure:** - GitHub Actions workflow (.github/workflows/deploy-library-api.yml) - Triggered by tags matching library-api-v* - Validates version sync between git tag and pom.xml - Builds Docker image using Dockerfile.jvm - Pushes to Google Artifact Registry with :v{version} and :latest tags - Deploys to Cloud Run with matching revision name **Version management:** - bin/tag-release: Atomically updates pom.xml and creates git tag - bin/validate-library-api-version: Pre-push hook validator - Semantic versioning: MAJOR.MINOR.PATCH (e.g., 0.3.0) **Docker:** - Multi-stage Kogito-based Dockerfile (Dockerfile.jvm) - Java 17 runtime on Red Hat UBI 9 - Optimized for Quarkus JVM mode **Configuration changes:** - pom.xml: Updated groupId to org.codeforphilly.bdt **Cloud Run target:** - Project: benefit-decision-toolkit-play - Region: us-central1 - Public/unauthenticated access Version tags, Docker tags, and Cloud Run revision names stay synchronized throughout the release process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent acc0f82 commit 10a8e36

File tree

5 files changed

+376
-4
lines changed

5 files changed

+376
-4
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# This workflow uses devbox for dependency management and builds/deploys the library API
2+
# to Cloud Run when a version tag is pushed (e.g., library-api-v1.0.0).
3+
4+
name: 'Build and Deploy Library API to Cloud Run'
5+
6+
on:
7+
push:
8+
tags:
9+
- 'library-api-v*'
10+
11+
env:
12+
PROJECT_ID: 'benefit-decision-toolkit-play'
13+
REGION: 'us-central1'
14+
SERVICE: 'benefit-decision-toolkit-play'
15+
API_NAME: 'library-api'
16+
WORKLOAD_IDENTITY_PROVIDER: 'projects/1034049717668/locations/global/workloadIdentityPools/github-actions-google-cloud/providers/github'
17+
18+
jobs:
19+
deploy:
20+
runs-on: 'ubuntu-latest'
21+
22+
permissions:
23+
contents: 'read'
24+
id-token: 'write'
25+
26+
steps:
27+
- name: 'Checkout'
28+
uses: 'actions/checkout@v4'
29+
30+
# Devbox needs a .env file to exist, even if it's empty
31+
- name: 'Create .env file'
32+
run: touch .env
33+
34+
# Setup devbox which includes all our dependencies: Maven, JDK 21, Quarkus, etc.
35+
- name: 'Install devbox'
36+
uses: 'jetify-com/[email protected]'
37+
with:
38+
enable-cache: true
39+
40+
# Extract version from pom.xml (source of truth) using Maven
41+
- name: 'Extract version from pom.xml'
42+
id: extract_version
43+
run: |
44+
# Use -f to specify the pom.xml path (devbox runs from repo root)
45+
VERSION=$(devbox run -q -- mvn -f library-api/pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout 2>&1 | tail -1 | xargs)
46+
47+
echo "Extracted VERSION: '${VERSION}'"
48+
49+
# Validate it's a semantic version
50+
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
51+
echo "ERROR: Invalid version format: '$VERSION'"
52+
echo "Expected semantic version (e.g., 0.1.2)"
53+
exit 1
54+
fi
55+
56+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
57+
# Create revision-safe version string (replace dots with dashes for Cloud Run)
58+
REVISION_VERSION=$(echo "${VERSION}" | tr '.' '-')
59+
echo "revision_version=${REVISION_VERSION}" >> "$GITHUB_OUTPUT"
60+
echo "Extracted version from pom.xml: ${VERSION}"
61+
echo "Revision version: ${REVISION_VERSION}"
62+
63+
# Validate that git tag exists for this pom.xml version
64+
- name: 'Validate git tag matches pom.xml version'
65+
run: |
66+
devbox run -q -- bin/validate-library-api-version
67+
68+
# Configure Workload Identity Federation and generate an access token
69+
- id: 'auth'
70+
name: 'Authenticate to Google Cloud'
71+
uses: 'google-github-actions/auth@v2'
72+
with:
73+
workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}'
74+
service_account: cicd-build-deploy-api@benefit-decision-toolkit-play.iam.gserviceaccount.com
75+
project_id: ${{ env.PROJECT_ID }}
76+
77+
# Configure Docker to use gcloud as a credential helper (using devbox gcloud)
78+
- name: 'Configure Docker'
79+
run: |
80+
devbox run -q -- gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev
81+
82+
# Build the Quarkus app with Maven using devbox environment
83+
- name: 'Build Quarkus App'
84+
working-directory: library-api
85+
run: |
86+
devbox run -q build-library-api-ci
87+
88+
- name: 'Build and Push Container'
89+
working-directory: library-api
90+
run: |-
91+
VERSION="${{ steps.extract_version.outputs.version }}"
92+
DOCKER_TAG_VERSIONED="${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.SERVICE }}/${{ env.API_NAME }}:v${VERSION}"
93+
DOCKER_TAG_LATEST="${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.SERVICE }}/${{ env.API_NAME }}:latest"
94+
95+
# Build and tag with version
96+
docker build -f src/main/docker/Dockerfile.jvm --tag "${DOCKER_TAG_VERSIONED}" --tag "${DOCKER_TAG_LATEST}" .
97+
98+
# Push both tags
99+
docker push "${DOCKER_TAG_VERSIONED}"
100+
docker push "${DOCKER_TAG_LATEST}"
101+
102+
echo "Pushed images:"
103+
echo " - ${DOCKER_TAG_VERSIONED}"
104+
echo " - ${DOCKER_TAG_LATEST}"
105+
106+
- name: 'Deploy to Cloud Run'
107+
id: deploy
108+
uses: 'google-github-actions/deploy-cloudrun@v2'
109+
with:
110+
service: '${{ env.API_NAME }}'
111+
region: '${{ env.REGION }}'
112+
image: '${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.SERVICE }}/${{ env.API_NAME }}:v${{ steps.extract_version.outputs.version }}'
113+
tag: '${{ env.API_NAME }}-v${{ steps.extract_version.outputs.revision_version }}'
114+
flags: '--allow-unauthenticated --max-instances=2 --service-account=library-api-service-account@${{ env.PROJECT_ID }}.iam.gserviceaccount.com'
115+
116+
# Show deployment output
117+
- name: 'Show deployment output'
118+
run: |
119+
echo "Deployment complete!"
120+
echo "Service URL: ${{ steps.deploy.outputs.url }}"
121+
echo "Version: v${{ steps.extract_version.outputs.version }}"
122+
echo "Revision: ${{ env.API_NAME }}-v${{ steps.extract_version.outputs.revision_version }}"

bin/validate-library-api-version

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env bash
2+
3+
# library-api Version Validation Script
4+
# Ensures pom.xml version matches git tag
5+
6+
set -e
7+
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
10+
LIBRARY_API_DIR="$REPO_ROOT/library-api"
11+
POM_FILE="$LIBRARY_API_DIR/pom.xml"
12+
13+
# Colors for output
14+
RED='\033[0;31m'
15+
GREEN='\033[0;32m'
16+
YELLOW='\033[1;33m'
17+
NC='\033[0m' # No Color
18+
19+
print_error() {
20+
echo -e "${RED}ERROR: $1${NC}" >&2
21+
}
22+
23+
print_success() {
24+
echo -e "${GREEN}$1${NC}"
25+
}
26+
27+
print_warning() {
28+
echo -e "${YELLOW}$1${NC}"
29+
}
30+
31+
# Check if we're in a git repository
32+
if ! git rev-parse --git-dir > /dev/null 2>&1; then
33+
print_error "Not in a git repository"
34+
exit 1
35+
fi
36+
37+
# Check if pom.xml exists
38+
if [ ! -f "$POM_FILE" ]; then
39+
print_error "pom.xml not found at $POM_FILE"
40+
exit 1
41+
fi
42+
43+
# Check if Maven is available
44+
if ! command -v mvn &> /dev/null; then
45+
print_error "Maven (mvn) not found. Please install Maven or use devbox."
46+
exit 1
47+
fi
48+
49+
# Extract version from pom.xml using Maven
50+
cd "$LIBRARY_API_DIR"
51+
POM_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout 2>/dev/null || echo "")
52+
53+
if [ -z "$POM_VERSION" ]; then
54+
print_error "Could not extract version from pom.xml"
55+
exit 1
56+
fi
57+
58+
# Expected git tag based on pom.xml version
59+
EXPECTED_TAG="library-api-v${POM_VERSION}"
60+
61+
# Check if we're being called in a pre-push hook context
62+
# In that case, check if a library-api-v* tag is being pushed
63+
if [ -n "$1" ] && [ "$1" = "--pre-push-hook" ]; then
64+
# Read stdin to get refs being pushed
65+
TAG_BEING_PUSHED=""
66+
while read local_ref local_sha remote_ref remote_sha; do
67+
# Check if it's a tag push matching library-api-v*
68+
if [[ "$remote_ref" =~ refs/tags/library-api-v.* ]]; then
69+
TAG_BEING_PUSHED="${remote_ref#refs/tags/}"
70+
71+
# Validate the tag matches pom.xml version
72+
if [ "$TAG_BEING_PUSHED" != "$EXPECTED_TAG" ]; then
73+
echo ""
74+
print_error "Version mismatch detected!"
75+
echo ""
76+
echo " Tag being pushed: $TAG_BEING_PUSHED"
77+
echo " Expected tag: $EXPECTED_TAG"
78+
echo " pom.xml version: $POM_VERSION"
79+
echo ""
80+
echo "To fix this issue:"
81+
echo " 1. Delete the incorrect tag: git tag -d $TAG_BEING_PUSHED"
82+
echo " 2. Use the helper script: cd library-api && ./bin/tag-release $POM_VERSION"
83+
echo ""
84+
exit 1
85+
fi
86+
87+
print_success "Version validation passed: $TAG_BEING_PUSHED matches pom.xml version $POM_VERSION"
88+
exit 0
89+
fi
90+
done
91+
92+
# No library-api tag being pushed, validation not needed
93+
exit 0
94+
fi
95+
96+
# Manual validation mode (not in pre-push hook)
97+
# Check if the expected tag exists locally or remotely
98+
TAG_EXISTS_LOCAL=$(git tag -l "$EXPECTED_TAG")
99+
TAG_EXISTS_REMOTE=$(git ls-remote --tags origin "$EXPECTED_TAG" 2>/dev/null | grep -c "$EXPECTED_TAG" || echo "0")
100+
101+
if [ -n "$TAG_EXISTS_LOCAL" ]; then
102+
print_success "Local tag $EXPECTED_TAG exists and matches pom.xml version $POM_VERSION"
103+
exit 0
104+
elif [ "$TAG_EXISTS_REMOTE" -gt 0 ]; then
105+
print_success "Remote tag $EXPECTED_TAG exists and matches pom.xml version $POM_VERSION"
106+
exit 0
107+
else
108+
print_warning "No git tag found for current pom.xml version"
109+
echo ""
110+
echo " pom.xml version: $POM_VERSION"
111+
echo " Expected tag: $EXPECTED_TAG"
112+
echo ""
113+
echo "To create a release tag:"
114+
echo " cd library-api && ./bin/tag-release $POM_VERSION"
115+
echo ""
116+
exit 1
117+
fi

library-api/bin/tag-release

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env bash
2+
3+
# library-api Release Tagging Script
4+
# This script ensures pom.xml version and git tag stay in sync
5+
6+
set -e
7+
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
LIBRARY_API_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
10+
POM_FILE="$LIBRARY_API_DIR/pom.xml"
11+
12+
# Colors for output
13+
RED='\033[0;31m'
14+
GREEN='\033[0;32m'
15+
YELLOW='\033[1;33m'
16+
NC='\033[0m' # No Color
17+
18+
print_error() {
19+
echo -e "${RED}ERROR: $1${NC}" >&2
20+
}
21+
22+
print_success() {
23+
echo -e "${GREEN}$1${NC}"
24+
}
25+
26+
print_info() {
27+
echo -e "${YELLOW}$1${NC}"
28+
}
29+
30+
# Check if version argument provided
31+
if [ $# -eq 0 ]; then
32+
print_error "No version specified"
33+
echo "Usage: $0 <version>"
34+
echo "Example: $0 1.0.0"
35+
exit 1
36+
fi
37+
38+
VERSION="$1"
39+
40+
# Validate semantic versioning format
41+
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
42+
print_error "Invalid version format: $VERSION"
43+
echo "Version must follow semantic versioning: MAJOR.MINOR.PATCH (e.g., 1.0.0)"
44+
exit 1
45+
fi
46+
47+
TAG_NAME="library-api-v${VERSION}"
48+
49+
print_info "Preparing to tag library-api release: $VERSION"
50+
echo ""
51+
52+
# Check if we're in a git repository
53+
if ! git rev-parse --git-dir > /dev/null 2>&1; then
54+
print_error "Not in a git repository"
55+
exit 1
56+
fi
57+
58+
# Check if tag already exists
59+
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
60+
print_error "Tag $TAG_NAME already exists"
61+
echo "Use 'git tag -d $TAG_NAME' to delete it locally if needed"
62+
exit 1
63+
fi
64+
65+
# Check if pom.xml exists
66+
if [ ! -f "$POM_FILE" ]; then
67+
print_error "pom.xml not found at $POM_FILE"
68+
exit 1
69+
fi
70+
71+
# Update pom.xml version using Maven
72+
print_info "Updating pom.xml version to $VERSION..."
73+
74+
cd "$LIBRARY_API_DIR"
75+
76+
# Use Maven versions plugin to update project version
77+
if ! mvn versions:set -DnewVersion="$VERSION" -DgenerateBackupPoms=false > /dev/null 2>&1; then
78+
print_error "Failed to update pom.xml version using Maven"
79+
exit 1
80+
fi
81+
82+
# Verify the update by extracting the project version
83+
CURRENT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout 2>/dev/null || echo "")
84+
85+
if [ "$CURRENT_VERSION" != "$VERSION" ]; then
86+
print_error "Failed to update pom.xml version"
87+
echo "Current version in pom.xml: $CURRENT_VERSION"
88+
echo "Expected version: $VERSION"
89+
exit 1
90+
fi
91+
92+
print_success "Updated pom.xml to version $VERSION"
93+
94+
# Stage pom.xml
95+
print_info "Staging pom.xml..."
96+
git add "$POM_FILE"
97+
print_success "Staged pom.xml"
98+
99+
# Create commit
100+
print_info "Creating commit..."
101+
git commit -m "Bump library-api version to $VERSION"
102+
print_success "Created commit"
103+
104+
# Create tag
105+
print_info "Creating git tag $TAG_NAME..."
106+
git tag -a "$TAG_NAME" -m "Release library-api $VERSION"
107+
print_success "Created tag $TAG_NAME"
108+
109+
echo ""
110+
print_success "Release preparation complete!"
111+
echo ""
112+
echo "Next steps:"
113+
echo " 1. Review the changes: git show"
114+
echo " 2. Push the commit: git push origin $(git rev-parse --abbrev-ref HEAD)"
115+
echo " 3. Push the tag: git push origin $TAG_NAME"
116+
echo ""
117+
echo "Pushing the tag will trigger the GitHub Actions deployment workflow."

library-api/pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
44
<modelVersion>4.0.0</modelVersion>
5-
<groupId>org.prestoncabe</groupId>
6-
<artifactId>benefit-decision-toolkit</artifactId>
7-
<version>v0.0.1</version>
8-
<name>Benefit Decision Toolkit</name>
5+
<groupId>org.codeforphilly.bdt</groupId>
6+
<artifactId>bdt-library-api</artifactId>
7+
<version>0.3.0</version>
8+
<name>BDT Library API</name>
99

1010
<properties>
1111
<compiler-plugin.version>3.11.0</compiler-plugin.version>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM registry.access.redhat.com/ubi9/openjdk-17:1.20
2+
3+
ENV LANGUAGE='en_US:en'
4+
5+
# We make four distinct layers so if there are application changes the library layers can be re-used
6+
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
7+
COPY --chown=185 target/quarkus-app/*.jar /deployments/
8+
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
9+
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
10+
11+
EXPOSE 8080
12+
USER 185
13+
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Dquarkus.http.port=${PORT:-8080} -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
14+
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
15+
16+
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

0 commit comments

Comments
 (0)