diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 847dc01ff37..2ae09309a6e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -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 \ No newline at end of file + 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 diff --git a/.github/workflows/reusable_discover-app-services.yaml b/.github/workflows/reusable_discover-app-services-all.yaml similarity index 96% rename from .github/workflows/reusable_discover-app-services.yaml rename to .github/workflows/reusable_discover-app-services-all.yaml index 84dc0cb0cf2..81308f19089 100644 --- a/.github/workflows/reusable_discover-app-services.yaml +++ b/.github/workflows/reusable_discover-app-services-all.yaml @@ -18,6 +18,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Discover Services id: discover diff --git a/.github/workflows/reusable_discover-app-services-changes.yaml b/.github/workflows/reusable_discover-app-services-changes.yaml new file mode 100644 index 00000000000..eca47e3ab8c --- /dev/null +++ b/.github/workflows/reusable_discover-app-services-changes.yaml @@ -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//... + 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" diff --git a/.github/workflows/security-scans.yaml b/.github/workflows/security-scans.yaml index 642aad7eb70..3759c32d13b 100644 --- a/.github/workflows/security-scans.yaml +++ b/.github/workflows/security-scans.yaml @@ -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 }}