Skip to content
Merged
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
159 changes: 106 additions & 53 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,99 +14,152 @@ env:

jobs:
discover_services:
name: Discover Microservices
uses: ./.github/workflows/reusable_discover-app-services.yaml
name: Discover Changed Microservices
uses: ./.github/workflows/reusable_discover-app-services-changes.yaml

create_sem_ver:
build_and_push:
needs: discover_services
if: ${{ needs.discover_services.outputs.services != '[]' }}

runs-on: ubuntu-latest

permissions:
contents: write # for creating git tags
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
pull-requests: write # for PR comments
id-token: write # for AWS OIDC (if used in future)

strategy:
matrix:
service: ${{ fromJson(needs.discover_services.outputs.services) }}
fail-fast: false

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Create version tag
id: create_version_tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SERVICE_NAME: ${{ matrix.service }}
- name: Calculate version and tags
id: version
run: |
CURRENT_VERSION_TAG=$(git ls-remote --tags origin "refs/tags/${SERVICE_NAME}-v*" | awk -F'/' '{print $3}' | sort -V | tail -n1)
if [ -z "$CURRENT_VERSION_TAG" ]; then
NEW_VERSION_TAG="${SERVICE_NAME}-v1.0.0"
SERVICE="${{ matrix.service }}"
SHORT_SHA=$(git rev-parse --short=7 HEAD)

# Find latest version tag for this service
LATEST_TAG=$(git tag -l "${SERVICE}-*" | grep -E "${SERVICE}-[0-9]+\.[0-9]+\.[0-9]+" | sort -V | tail -n1)

if [ -z "$LATEST_TAG" ]; then
# No previous version, start with 1.0.0
VERSION="1.0.0"
else
VERSION_NUMBER=${CURRENT_VERSION_TAG#${SERVICE_NAME}-v}
# Extract version and increment patch
VERSION_NUMBER=${LATEST_TAG#${SERVICE}-}
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION_NUMBER"
PATCH=$((PATCH + 1))
NEW_VERSION_TAG="${SERVICE_NAME}-v${MAJOR}.${MINOR}.${PATCH}"
VERSION="${MAJOR}.${MINOR}.${PATCH}"
fi
echo "New version tag for service ${SERVICE_NAME}: $NEW_VERSION_TAG"
echo "version_tag=$NEW_VERSION_TAG" >> "$GITHUB_OUTPUT"

build_and_scan:
needs: discover_services
if: ${{ needs.discover_services.outputs.services != '[]' }}
# Construct version-sha tag (immutable)
VERSION_SHA="${VERSION}-${SHORT_SHA}"

runs-on: ubuntu-latest

permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
pull-requests: write # for creating pull requests with scan results
echo "service=${SERVICE}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "version_sha=${VERSION_SHA}" >> "$GITHUB_OUTPUT"

strategy:
matrix:
service: ${{ fromJson(needs.discover_services.outputs.services) }}
echo "- Service: ${SERVICE}"
echo "- Version: ${VERSION}"
echo "- SHA: ${SHORT_SHA}"
echo "- Immutable tag: ${VERSION_SHA}"

env:
IMAGE_TAG: ${{ matrix.service }}:ci-${{ github.sha }}
- name: Create git tag (only on push to main)
if: github.event_name == 'push'
run: |
SERVICE="${{ steps.version.outputs.service }}"
VERSION="${{ steps.version.outputs.version }}"
GIT_TAG="${SERVICE}-${VERSION}"

steps:
- uses: actions/checkout@v4
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Create annotated tag
git tag -a "$GIT_TAG" -m "Release ${VERSION} for ${SERVICE}" \
-m "Commit: ${{ github.sha }}" \
-m "Triggered by: ${{ github.actor }}"

- name: Trivy scan
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: '${{ env.IMAGE_TAG }}'
format: 'sarif'
output: 'trivy-results.sarif'
exit-code: '0'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'

- name: Upload Trivy SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
git push origin "$GIT_TAG"

echo "Created git tag: ${GIT_TAG}"

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.AWS_ROLE_TO_ASSUME }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}

- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2

- name: Prepare image tags
id: tags
run: |
SERVICE="${{ steps.version.outputs.service }}"
VERSION="${{ steps.version.outputs.version }}"
VERSION_SHA="${{ steps.version.outputs.version_sha }}"
REGISTRY="${{ env.ECR_REGISTRY_URI }}"

# Base tags (always created)
TAGS="${REGISTRY}/${SERVICE}:${VERSION_SHA}"

# Dev tags (for PR and main branch)
TAGS="${TAGS},${REGISTRY}/${SERVICE}:dev-${VERSION}"
TAGS="${TAGS},${REGISTRY}/${SERVICE}:dev-latest"

echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"

echo "- Image tags to be created:"
echo " ${TAGS}" | tr ',' '\n' | sed 's/^/ - /'

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
- name: Build and push to ECR
uses: docker/build-push-action@v6
with:
context: ./microservices-demo/src/${{ matrix.service }}
file: ./microservices-demo/src/${{ matrix.service }}/Dockerfile
platforms: linux/amd64
push: true
tags: |
${{ env.ECR_REGISTRY_URI }}/${{ matrix.service }}:${{ github.ref_name }}
${{ env.ECR_REGISTRY_URI }}/${{ matrix.service }}:latest
tags: ${{ steps.tags.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
labels: |
org.opencontainers.image.title=${{ matrix.service }}
org.opencontainers.image.version=${{ steps.version.outputs.version }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.created=${{ github.event.head_commit.timestamp }}

- name: Build summary
run: |
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Service:** ${{ matrix.service }}" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "**Immutable tag:** ${{ steps.version.outputs.version_sha }}" >> $GITHUB_STEP_SUMMARY
echo "**Git SHA:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Image Tags Created" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "${{ steps.tags.outputs.tags }}" | tr ',' '\n' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

if [ "${{ github.event_name }}" == "push" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Auto-deployed to dev environment**" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Available for testing (dev tags created)**" >> $GITHUB_STEP_SUMMARY
fi
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Discover Services
id: discover
Expand Down
106 changes: 106 additions & 0 deletions .github/workflows/reusable_discover-app-services-changes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Discover Changed Microservices

on:
workflow_call:
outputs:
services:
description: "JSON array of changed microservice names"
value: ${{ jobs.discover-services.outputs.services }}

jobs:
discover-services:
name: Discover Changed Microservices
runs-on: ubuntu-latest

outputs:
services: ${{ steps.discover.outputs.services }}

steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Discover Changed Services
id: discover
run: |
# Determine the base commit for comparison
if [ "${{ github.event_name }}" == "pull_request" ]; then
# For PRs, compare against the base branch
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.sha }}"
echo "Comparing PR: ${BASE_SHA:0:7}...${HEAD_SHA:0:7}"
elif [ "${{ github.event_name }}" == "push" ]; then
# For pushes, compare with previous commit
# Check if this is the first commit (no parent)
if git rev-parse HEAD~1 >/dev/null 2>&1; then
BASE_SHA="HEAD~1"
HEAD_SHA="HEAD"
echo "Comparing push: previous commit vs current"
else
# First commit - compare with empty tree
BASE_SHA="4b825dc642cb6eb9a060e54bf8d69288fbee4904" # Git empty tree SHA
HEAD_SHA="HEAD"
echo "First commit detected - checking all files"
fi
else
echo "Unsupported event type: ${{ github.event_name }}"
echo "services=[]" >> "$GITHUB_OUTPUT"
exit 0
fi

# Get list of changed files in microservices-demo/src/
CHANGED_FILES=$(git diff --name-only ${BASE_SHA} ${HEAD_SHA} -- microservices-demo/src/ || true)

if [ -z "$CHANGED_FILES" ]; then
echo "No changes detected in microservices-demo/src/"
echo "services=[]" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "Changed files:"
echo "$CHANGED_FILES"
echo ""

# Extract unique service names from changed file paths
# Expected path format: microservices-demo/src/<service-name>/...
CHANGED_SERVICES=$(echo "$CHANGED_FILES" \
| grep "^microservices-demo/src/" \
| cut -d'/' -f3 \
| sort -u \
| grep -v "^$" || true)

if [ -z "$CHANGED_SERVICES" ]; then
echo "No service-level changes detected"
echo "services=[]" >> "$GITHUB_OUTPUT"
exit 0
fi

# Verify that changed directories have Dockerfiles
VALID_SERVICES=()
for SERVICE in $CHANGED_SERVICES; do
SERVICE_PATH="microservices-demo/src/${SERVICE}"

# Check if directory exists and has a Dockerfile
if [ -d "$SERVICE_PATH" ] && [ -f "$SERVICE_PATH/Dockerfile" ]; then
VALID_SERVICES+=("$SERVICE")
echo " ${SERVICE} (has Dockerfile)"
else
echo "${SERVICE} (skipped - no Dockerfile)"
fi
done

# Convert to JSON array
if [ ${#VALID_SERVICES[@]} -eq 0 ]; then
echo ""
echo "No valid services with Dockerfiles found"
SERVICES_JSON="[]"
else
SERVICES_JSON=$(printf '%s\n' "${VALID_SERVICES[@]}" | jq -R . | jq -sc .)
fi

echo ""
echo "====================================="
echo " Changed services: $SERVICES_JSON"
echo "====================================="
echo "services=$SERVICES_JSON" >> "$GITHUB_OUTPUT"
2 changes: 1 addition & 1 deletion .github/workflows/security-scans.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:

discover-services:
name: Discover Microservices
uses: ./.github/workflows/reusable_discover-app-services.yaml
uses: ./.github/workflows/reusable_discover-app-services-all.yaml

scan-ecr-images:
name: Scan ECR Images - ${{ matrix.environment }}/${{ matrix.service }}
Expand Down