Skip to content

[deps] Update dawidd6/action-send-mail action to v16 #7573

[deps] Update dawidd6/action-send-mail action to v16

[deps] Update dawidd6/action-send-mail action to v16 #7573

Workflow file for this run

name: Run XTM Hub CI/CD
on:
workflow_dispatch:
push:
branches:
- "main"
- "development"
paths:
- "apps/**"
- ".github/workflows/dockerbuild-ci.yml"
- "package.json"
- "yarn.lock"
tags:
- "v*"
pull_request:
branches:
- "main"
- "development"
- "issue/*"
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
code:
- "apps/**"
- ".github/workflows/dockerbuild-ci.yml"
- "package.json"
- "yarn.lock"
run-lint:
name: Lint ${{ matrix.label }}
needs: detect-changes
if: needs.detect-changes.outputs.code == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- label: portal-api
working-directory: apps/portal-api
- label: portal-front
working-directory: apps/portal-front
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@4.13.0 --activate
- name: Install dependencies
run: yarn install --immutable
- name: Run lint
working-directory: ${{ matrix.working-directory }}
run: yarn lint
build-images-tests:
needs:
- detect-changes
if: needs.detect-changes.outputs.code == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
packages: write
strategy:
fail-fast: true
matrix:
include:
- image: portal-front
dockerfile: apps/portal-front/Dockerfile
- image: portal-api
dockerfile: apps/portal-api/Dockerfile
- image: portal-e2e-tests
dockerfile: apps/portal-e2e-tests/Dockerfile
- image: portal-front-test
dockerfile: apps/portal-front/test.Dockerfile
- image: portal-api-test
dockerfile: apps/portal-api/test.Dockerfile
outputs:
docker-tags: ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
docker-labels: ${{ steps.meta.outputs.labels }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Setup Docker layer cache
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ matrix.image }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-${{ matrix.image }}-
${{ runner.os }}-buildx-
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta deps
id: meta
uses: docker/metadata-action@v5
with:
images: filigran/${{ matrix.image }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Copy migrations
run: |
cp -r ./apps/portal-api/src/migrations ./apps/portal-e2e-tests/migrations
cp -r ./apps/portal-api/tests/seeds ./apps/portal-e2e-tests/seeds
cp ./apps/portal-api/tests/test-utils.ts ./apps/portal-e2e-tests/test-utils.ts
- name: Build and push portal image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
APP_VERSION=0.0.0-e2e
cache-from: |
type=local,src=/tmp/.buildx-cache
type=registry,ref=filigran/${{ matrix.image }}:main
type=registry,ref=filigran/${{ matrix.image }}:development
type=registry,ref=filigran/${{ matrix.image }}:cache-${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
cache-to: |
type=local,dest=/tmp/.buildx-cache-new,mode=max
type=registry,ref=filigran/${{ matrix.image }}:cache-${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }},mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
run-e2e-tests:
needs: build-images-tests
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Install docker
uses: docker/setup-docker-action@v4
- name: Copy migrations
run: |
cp -r ./apps/portal-api/src/migrations ./apps/portal-e2e-tests/migrations
cp -r ./apps/portal-api/tests/seeds ./apps/portal-e2e-tests/seeds
cp ./apps/portal-api/tests/test-utils.ts ./apps/portal-e2e-tests/test-utils.ts
- name: Launch docker compose
run: |
mkdir -p ctrf
export IMAGE_TAGS="${{ needs.build-images-tests.outputs.docker-tags }}" \
TEAMS_WEBHOOK_E2E="${{ secrets.TEAMS_WEBHOOK_E2E }}" \
GITHUB_PR_TITLE="${{ github.event.pull_request.title }}" \
GITHUB_PR_NUMBER="${{ github.event.pull_request.number }}" \
GITHUB_RUN_ID="${{ github.run_id }}"
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml run portal-e2e-tests || TEST_EXIT_CODE=$?
CONTAINER_ID=$(docker ps -a --filter "name=portal-e2e-tests-run" --format "{{.ID}}" | head -1)
docker cp $CONTAINER_ID:/app/apps/portal-e2e-tests/ctrf/. ./ctrf/
exit ${TEST_EXIT_CODE:-0}
- name: Publish Test Report
if: always()
uses: ctrf-io/github-test-reporter@v1
with:
report-path: "./ctrf/*.json"
pull-request: true
summary-report: true
failed-report: true
flaky-report: true
insights-report: true
suite-folded-report: true
overwrite-comment: true
report-order: "summary-report,failed-report,flaky-report,insights-report,test-report"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Collect service logs on failure
if: failure()
run: |
echo "=== Collecting logs from all services ==="
echo ""
echo "=== Portal API Logs ==="
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs --tail=100 portal-api || echo "Could not collect portal-api logs"
echo ""
echo "=== Portal API Test Logs ==="
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs --tail=100 portal-api-test || echo "Could not collect portal-api-test logs"
echo ""
echo "=== Portal Front Logs ==="
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs --tail=100 portal-front || echo "Could not collect portal-front logs"
echo ""
echo "=== Portal Front Test Logs ==="
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs --tail=100 portal-front-test || echo "Could not collect portal-front-test logs"
echo ""
echo "=== PostgreSQL Logs ==="
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs --tail=100 portal-postgres || echo "Could not collect portal-postgres logs"
echo ""
echo "=== MinIO Logs ==="
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs --tail=100 portal-minio || echo "Could not collect portal-minio logs"
echo ""
echo "=== E2E Tests Logs ==="
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs --tail=100 portal-e2e-tests || echo "Could not collect portal-e2e-tests logs"
echo ""
echo "=== Docker container status ==="
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml ps -a || echo "Could not get container status"
- name: Save logs as artifacts
if: failure()
run: |
mkdir -p logs
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs portal-api > logs/portal-api.log 2>&1 || true
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs portal-api-test > logs/portal-api-test.log 2>&1 || true
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs portal-front > logs/portal-front.log 2>&1 || true
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs portal-front-test > logs/portal-front-test.log 2>&1 || true
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs portal-postgres > logs/portal-postgres.log 2>&1 || true
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs portal-minio > logs/portal-minio.log 2>&1 || true
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml logs portal-e2e-tests > logs/portal-e2e-tests.log 2>&1 || true
- name: Upload logs
if: failure()
uses: actions/upload-artifact@v7
with:
name: service-logs-${{ github.run_id }}
path: logs/
retention-days: 30
run-front-unit-tests:
needs: build-images-tests
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
packages: write
if: contains(github.ref , 'refs/tags/') == false
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Install docker
uses: docker/setup-docker-action@v4
- name: Run front unit tests
run: |
export IMAGE_TAGS=${{ needs.build-images-tests.outputs.docker-tags }}
mkdir -p coverage
docker run --rm \
-v $(pwd)/coverage:/app/apps/portal-front/coverage \
filigran/portal-front-test:${IMAGE_TAGS}
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
run-api-unit-tests:
needs: build-images-tests
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
packages: write
if: contains(github.ref , 'refs/tags/') == false
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Install docker
uses: docker/setup-docker-action@v4
- name: Launch docker compose
run: |
mkdir -p coverage
export IMAGE_TAGS=${{ needs.build-images-tests.outputs.docker-tags }}
docker compose -f ./xtm-hub-dev/docker-compose-ci.yml run portal-api-test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
deploy-feature-branch:
needs: build-images-tests
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
if: |
github.event_name == 'pull_request' &&
!contains(github.event.pull_request.labels.*.name, 'skip-feature-env')
steps:
- name: Install AWX cli
run: pip install awxkit
- name: Deploy
run: |
max_attempts=5
for i in $(seq 1 $max_attempts); do
if awx --conf.host https://awx.filigran.io \
--conf.token ${{ secrets.AWX_TOKEN }} \
-f human job_templates launch 'Deploy XTM Hub feature branch for testing' \
--inventory eu-west-staging \
--extra_vars "{\"env\":\"Development\",\"xtmhub_version\":\"pr-${{ github.event.number }}\",\"xtmhub_node_env\":\"development\"}"; then
echo "✅ Deployment successful"
exit 0
fi
if [ $i -lt $max_attempts ]; then
delay=$((2 ** i))
echo "⚠️ Attempt $i/$max_attempts failed, retrying in ${delay}s..."
sleep $delay
fi
done
echo "❌ All $max_attempts attempts failed"
exit 1
build-images-prod:
needs:
- run-lint
- run-e2e-tests
- run-front-unit-tests
- run-api-unit-tests
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
packages: write
strategy:
fail-fast: true
matrix:
include:
- image: portal-front
dockerfile: apps/portal-front/Dockerfile
- image: portal-api
dockerfile: apps/portal-api/Dockerfile
if: |
always() &&
github.event_name != 'pull_request' &&
(needs.run-lint.result == 'success') &&
(needs.run-e2e-tests.result == 'success') &&
(needs.run-front-unit-tests.result == 'skipped' || needs.run-front-unit-tests.result == 'success') &&
(needs.run-api-unit-tests.result == 'skipped' || needs.run-api-unit-tests.result == 'success')
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Setup Docker layer cache
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-prod-${{ matrix.image }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-prod-${{ matrix.image }}-
${{ runner.os }}-buildx-${{ matrix.image }}-
${{ runner.os }}-buildx-
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta deps
id: meta
uses: docker/metadata-action@v5
with:
images: filigran/${{ matrix.image }}-prod
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
${{ contains(github.ref, 'refs/tags/') && 'latest' }}
- name: Determine version
id: version
run: |
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
echo "VERSION=${GITHUB_REF_NAME}" >> $GITHUB_ENV
elif [[ "${GITHUB_REF_NAME}" == "main" ]]; then
echo "VERSION=staging-${GITHUB_SHA::8}" >> $GITHUB_ENV
elif [[ "${GITHUB_REF_NAME}" == "development" ]]; then
echo "VERSION=dev-${GITHUB_SHA::8}" >> $GITHUB_ENV
else
echo "VERSION=local-${GITHUB_SHA::8}" >> $GITHUB_ENV
fi
- name: Build and push images
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }}
build-args: |
APP_VERSION=${{ env.VERSION }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: |
type=local,src=/tmp/.buildx-cache
type=registry,ref=filigran/${{ matrix.image }}:main
type=registry,ref=filigran/${{ matrix.image }}:development
type=registry,ref=filigran/${{ matrix.image }}:latest
type=registry,ref=filigran/${{ matrix.image }}:${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
deploy:
needs: build-images-prod
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
if: |
always() &&
github.event_name != 'pull_request' &&
(needs.build-images-prod.result == 'success')
steps:
- name: Install AWX cli
run: pip install awxkit
- name: Deploy on Production
if: contains(github.ref, 'refs/tags/')
run: |
export VERSION=$(echo "${GITHUB_REF_NAME}" | sed 's/^v//')
max_attempts=5
for i in $(seq 1 $max_attempts); do
if awx --conf.host https://awx.filigran.io \
--conf.token ${{ secrets.AWX_TOKEN }} \
-f human job_templates launch 'Deploy XTM Hub' \
--inventory eu-west-production \
--extra_vars "{\"env\":\"Production\",\"xtmhub_version\":\"$VERSION\"}"; then
echo "✅ Deployment successful"
exit 0
fi
if [ $i -lt $max_attempts ]; then
delay=$((2 ** i))
echo "⚠️ Attempt $i/$max_attempts failed, retrying in ${delay}s..."
sleep $delay
fi
done
echo "❌ All $max_attempts attempts failed"
exit 1
- name: Send n8n Webhook Notification
if: contains(github.ref, 'refs/tags/')
run: |
echo "Sending n8n webhook notification..."
response=$(curl -X POST \
-w "\n%{http_code}" \
-o /tmp/webhook_response.txt \
"${{ secrets.N8N_NOTIF_DEPLOYMENT_WEBHOOK }}?version=${GITHUB_REF_NAME}&status=completed")
http_code=$(echo "$response" | tail -n1)
response_body=$(cat /tmp/webhook_response.txt)
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo "✅ Webhook notification sent successfully (HTTP $http_code)"
echo "Response: $response_body"
else
echo "⚠️ Webhook notification failed (HTTP $http_code)"
echo "Response: $response_body"
fi
- name: Deploy on Staging
if: github.ref == 'refs/heads/main'
run: |
max_attempts=5
for i in $(seq 1 $max_attempts); do
if awx --conf.host https://awx.filigran.io \
--conf.token ${{ secrets.AWX_TOKEN }} \
-f human job_templates launch 'Deploy XTM Hub' \
--inventory eu-west-staging \
--extra_vars '{"env":"Staging"}'; then
echo "✅ Deployment successful"
exit 0
fi
if [ $i -lt $max_attempts ]; then
delay=$((2 ** i))
echo "⚠️ Attempt $i/$max_attempts failed, retrying in ${delay}s..."
sleep $delay
fi
done
echo "❌ All $max_attempts attempts failed"
exit 1
- name: Deploy on Development
if: github.ref == 'refs/heads/development'
run: |
max_attempts=5
for i in $(seq 1 $max_attempts); do
if awx --conf.host https://awx.filigran.io \
--conf.token ${{ secrets.AWX_TOKEN }} \
-f human job_templates launch 'Deploy XTM Hub' \
--inventory eu-west-staging \
--extra_vars '{"env":"Development"}'; then
echo "✅ Deployment successful"
exit 0
fi
if [ $i -lt $max_attempts ]; then
delay=$((2 ** i))
echo "⚠️ Attempt $i/$max_attempts failed, retrying in ${delay}s..."
sleep $delay
fi
done
echo "❌ All $max_attempts attempts failed"
exit 1
- name: Deploy on Prerelease
if: github.ref == 'refs/heads/development'
run: |
max_attempts=5
for i in $(seq 1 $max_attempts); do
if awx --conf.host https://awx.filigran.io \
--conf.token ${{ secrets.AWX_TOKEN }} \
-f human job_templates launch 'Deploy XTM Hub' \
--inventory eu-west-staging \
--extra_vars '{"env":"Prerelease"}'; then
echo "✅ Deployment successful"
exit 0
fi
if [ $i -lt $max_attempts ]; then
delay=$((2 ** i))
echo "⚠️ Attempt $i/$max_attempts failed, retrying in ${delay}s..."
sleep $delay
fi
done
echo "❌ All $max_attempts attempts failed"
exit 1