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
108 changes: 45 additions & 63 deletions .github/actions/build-docker/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ inputs:
description: 'GitHub Container Registry Personal Access Token'
DOCKER_USER:
required: false
description: 'DockerHub username for private base image pulls'
description: 'DockerHub username (required for FIPS builds to pull private base image)'
DOCKER_PASS:
required: false
description: 'DockerHub password/token for private base image pulls'
description: 'DockerHub password (required for FIPS builds to pull private base image)'
deno-version:
required: true
description: 'Deno version'
Expand Down Expand Up @@ -49,8 +49,9 @@ runs:
username: ${{ inputs.CR_USER }}
password: ${{ inputs.CR_PAT }}

- name: Login to DockerHub for FIPS base images
if: inputs.type == 'fips' && inputs.DOCKER_USER != '' && inputs.DOCKER_PASS != ''
- name: Login to DockerHub
# FIPS base image (rocketchat/dhi-node) lives in a private DockerHub repo and requires auth to pull.
if: inputs.type == 'fips' && github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop')
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
username: ${{ inputs.DOCKER_USER }}
Expand Down Expand Up @@ -88,23 +89,21 @@ runs:
- name: Build Docker images
shell: bash
env:
INPUT_DENO_VERSION: ${{ inputs.deno-version }}
DENO_VERSION: ${{ inputs.deno-version }}
INPUT_ARCH: ${{ inputs.arch }}
INPUT_SERVICE: ${{ inputs.service }}
INPUT_PUBLISH_IMAGE: ${{ inputs.publish-image }}
INPUT_TYPE: ${{ inputs.type }}
GH_RUN_ID: ${{ github.run_id }}
GH_SERVER_URL: ${{ github.server_url }}
GH_REPOSITORY: ${{ github.repository }}
GH_EVENT_NAME: ${{ github.event_name }}
GH_REF: ${{ github.ref }}
SERVICE_SUFFIX_FROM_CONTEXT: ${{ (inputs.service == 'rocketchat' && inputs.type == 'coverage' && (github.event_name == 'release' || github.ref == 'refs/heads/develop')) && '-cov' || (inputs.type == 'fips' && '-fips' || '') }}
GITHUB_RUN_ID: ${{ github.run_id }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_REF: ${{ github.ref }}
run: |
set -o xtrace
export DENO_VERSION="$INPUT_DENO_VERSION"
compose_files=(-f docker-compose-ci.yml)
compose_fips_override=''
if [[ "$INPUT_TYPE" == 'fips' ]]; then
compose_files+=(-f docker-compose-ci.fips.yml)
compose_fips_override='-f docker-compose-ci.fips.yml'
fi

# Removes unnecessary swc cores and sharp binaries to reduce image size
Expand All @@ -129,51 +128,34 @@ runs:
LOAD_OR_PUSH="--load"
fi

export DOCKER_CLIENT_TIMEOUT=300
export COMPOSE_HTTP_TIMEOUT=300

# Get image name from compose config since rocketchat image is different from service name (rocket.chat)
IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image')

BUILD_RUN_URL="${GH_SERVER_URL}/${GH_REPOSITORY}/actions/runs/${GH_RUN_ID}"
BUILD_SOURCE_URL="${GH_SERVER_URL}/${GH_REPOSITORY}"

buildx_bake_cmd=(
docker buildx bake
"${compose_files[@]}"
${LOAD_OR_PUSH}
--allow=fs.read=/tmp/build
--set "*.tags+=${IMAGE}-gha-run-${GH_RUN_ID}"
--set "*.labels.org.opencontainers.image.description=Build run: ${BUILD_RUN_URL}"
--set "*.labels.org.opencontainers.image.source=${BUILD_SOURCE_URL}"
--set "*.platform=linux/${INPUT_ARCH}"
--set *.cache-from=type=gha
--set *.cache-to=type=gha,mode=max
--provenance=false
--sbom=false
--metadata-file "/tmp/meta.json"
# Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat)
IMAGE=$(docker compose -f docker-compose-ci.yml $compose_fips_override config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image')

docker buildx bake \
-f docker-compose-ci.yml $compose_fips_override \
${LOAD_OR_PUSH} \
--allow=fs.read=/tmp/build \
--set "*.tags+=${IMAGE}-gha-run-${GITHUB_RUN_ID}" \
--set "*.labels.org.opencontainers.image.description=Build run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
--set "*.labels.org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \
--set "*.platform=linux/${INPUT_ARCH}" \
--set *.cache-from=type=gha \
--set *.cache-to=type=gha,mode=max \
--provenance=false \
--sbom=false \
--metadata-file "/tmp/meta.json" \
"$INPUT_SERVICE"
)

attempts=1
max_attempts=3
until "${buildx_bake_cmd[@]}"; do
if [[ "$INPUT_PUBLISH_IMAGE" != 'true' || $attempts -ge $max_attempts ]]; then
echo "docker buildx bake failed after ${attempts} attempt(s)."
exit 1
fi

attempts=$((attempts + 1))
sleep_seconds=$((15 * attempts))
echo "docker buildx bake failed (likely transient push error). Retrying in ${sleep_seconds}s... (attempt ${attempts}/${max_attempts})"
sleep "${sleep_seconds}"
done

echo "Contents of /tmp/meta.json:"
cat /tmp/meta.json

if [[ "$INPUT_PUBLISH_IMAGE" == 'true' ]]; then
SERVICE_SUFFIX="$SERVICE_SUFFIX_FROM_CONTEXT"
SERVICE_SUFFIX=''
if [[ "$INPUT_SERVICE" == 'rocketchat' && "$INPUT_TYPE" == 'coverage' ]] && [[ "$GITHUB_EVENT_NAME" == 'release' || "$GITHUB_REF" == 'refs/heads/develop' ]]; then
SERVICE_SUFFIX='-cov'
elif [[ "$INPUT_TYPE" == 'fips' ]]; then
SERVICE_SUFFIX='-fips'
fi

mkdir -p "/tmp/manifests/${INPUT_SERVICE}${SERVICE_SUFFIX}/${INPUT_ARCH}"

Expand All @@ -195,26 +177,26 @@ runs:
if: inputs.publish-image == 'false' && inputs.arch == 'amd64'
shell: bash
env:
INPUT_TYPE: ${{ inputs.type }}
INPUT_SERVICE: ${{ inputs.service }}
INPUT_ARCH: ${{ inputs.arch }}
SERVICE: ${{ inputs.service }}
ARCH: ${{ inputs.arch }}
TYPE: ${{ inputs.type }}
run: |
set -o xtrace
compose_files=(-f docker-compose-ci.yml)
if [[ "$INPUT_TYPE" == 'fips' ]]; then
compose_files+=(-f docker-compose-ci.fips.yml)
compose_fips_override=''
if [[ "$TYPE" == 'fips' ]]; then
compose_fips_override='-f docker-compose-ci.fips.yml'
fi

# Get image name from compose config
IMAGE=$(docker compose "${compose_files[@]}" config --format json 2>/dev/null | jq -r --arg s "$INPUT_SERVICE" '.services[$s].image')
# Get image name from docker-compose-ci.yml
IMAGE=$(docker compose -f docker-compose-ci.yml $compose_fips_override config --format json 2>/dev/null | jq -r --arg s "$SERVICE" '.services[$s].image')

# Create directory for image archives
mkdir -p /tmp/docker-images

# Save the image to a tar file
docker save "${IMAGE}" -o "/tmp/docker-images/${INPUT_SERVICE}-${INPUT_ARCH}-${INPUT_TYPE}.tar"
docker save "${IMAGE}" -o "/tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar"

echo "Saved image to /tmp/docker-images/${INPUT_SERVICE}-${INPUT_ARCH}-${INPUT_TYPE}.tar"
echo "Saved image to /tmp/docker-images/${SERVICE}-${ARCH}-${TYPE}.tar"
ls -lh /tmp/docker-images/

- name: Upload Docker image artifact
Expand Down
145 changes: 15 additions & 130 deletions .github/workflows/ci-test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,6 @@ jobs:
name: MongoDB ${{ matrix.mongodb-version }}${{ inputs.coverage == matrix.mongodb-version && ' coverage' || '' }} (${{ matrix.shard }}/${{ inputs.total-shard }})

steps:
- name: Set compose files
env:
INPUT_RELEASE: ${{ inputs.release }}
run: |
if [[ "$INPUT_RELEASE" == 'fips' ]]; then
echo 'COMPOSE_FILES=-f docker-compose-ci.yml -f docker-compose-ci.fips.yml' >> "$GITHUB_ENV"
echo 'COMPOSE_FILES_METEOR=-f ../../docker-compose-ci.yml -f ../../docker-compose-ci.fips.yml' >> "$GITHUB_ENV"
else
echo 'COMPOSE_FILES=-f docker-compose-ci.yml' >> "$GITHUB_ENV"
echo 'COMPOSE_FILES_METEOR=-f ../../docker-compose-ci.yml' >> "$GITHUB_ENV"
fi

- name: Collect Workflow Telemetry
if: inputs.type == 'perf'
uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0
Expand Down Expand Up @@ -146,7 +134,7 @@ jobs:
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
if: github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop'
with:
pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.release == 'fips' && 'docker-image-*-amd64-*' || 'docker-image-*-amd64-coverage') }}
pattern: ${{ inputs.release == 'ce' && 'docker-image-rocketchat-amd64-coverage' || (inputs.release == 'fips' && 'docker-image-*-amd64-fips' || 'docker-image-*-amd64-coverage') }}
path: /tmp/docker-images
merge-multiple: true

Expand Down Expand Up @@ -176,8 +164,7 @@ jobs:
- name: Start httpbin container and wait for it to be ready
if: inputs.type == 'api' || inputs.type == 'api-livechat'
run: |
read -r -a compose_files <<< "$COMPOSE_FILES"
docker compose "${compose_files[@]}" up -d httpbin
docker compose -f docker-compose-ci.yml up -d httpbin

- name: Prepare code coverage directory
run: |
Expand All @@ -186,132 +173,36 @@ jobs:
mkdir -p "$COVERAGE_DIR"
chmod 777 "$COVERAGE_DIR"

- name: Pull EE/FIPS images
if: (inputs.release == 'ee' || inputs.release == 'fips') && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop')
run: |
read -r -a compose_files <<< "$COMPOSE_FILES"
docker compose "${compose_files[@]}" pull

- name: Start containers for CE
if: inputs.release == 'ce'
run: |
# when we are testing CE, we only need to start the rocketchat container
read -r -a compose_files <<< "$COMPOSE_FILES"
DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d rocketchat --wait
DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d rocketchat --wait

- name: Start containers for EE
if: inputs.release == 'ee' || inputs.release == 'fips'
env:
ENTERPRISE_LICENSE: ${{ inputs.enterprise-license }}
TRANSPORTER: ${{ inputs.transporter }}
COMPOSE_PROFILES: ${{ inputs.type == 'api' && 'api' || '' }}
FIPS_OVERRIDE: ${{ inputs.release == 'fips' && '-f docker-compose-ci.fips.yml' || '' }}
run: |
read -r -a compose_files <<< "$COMPOSE_FILES"
DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose "${compose_files[@]}" up -d --wait --no-build
read -r -a fips_override <<< "$FIPS_OVERRIDE"
DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml "${fips_override[@]}" up -d --wait

- uses: ./.github/actions/setup-playwright
if: inputs.type == 'ui'

- name: Wait services to start up
if: inputs.release == 'ee' || inputs.release == 'fips'
run: |
read -r -a compose_files <<< "$COMPOSE_FILES"
docker ps

wait_for_mongo_primary() {
local retries=36
local delay=5

for attempt in $(seq 1 "$retries"); do
local is_primary
is_primary=$(docker compose "${compose_files[@]}" exec -T mongo mongosh --quiet --eval "try { const hello = db.hello(); print((hello.isWritablePrimary || hello.ismaster) ? '1' : '0'); } catch (e) { print('0'); }" 2>/dev/null | tail -n1 || true)

if [[ "$is_primary" == '1' ]]; then
echo "Mongo replica set primary is ready"
return 0
fi

echo "Waiting for Mongo primary (attempt ${attempt}/${retries})"
sleep "$delay"
done

echo "Mongo primary was not ready in time"
docker compose "${compose_files[@]}" logs mongo
return 1
}

wait_for_service() {
local service="$1"
local retries=18
local delay=10
local broker_service_name="${service%-service}"
local ready_pattern="NetworkBroker started successfully|ServiceBroker with [0-9]+ service\(s\) started successfully|Service '${broker_service_name}' started\."

for attempt in $(seq 1 "$retries"); do
local container_id
container_id=$(docker compose "${compose_files[@]}" ps -q "$service")

if [[ -z "$container_id" ]]; then
echo "Service '$service' has no container ID"
docker compose "${compose_files[@]}" ps
return 1
fi

local container_state
container_state=$(docker inspect -f '{{.State.Status}}' "$container_id" 2>/dev/null || echo "unknown")
local health_state
health_state=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id" 2>/dev/null || echo "unknown")

if [[ "$container_state" != 'running' ]]; then
echo "Service '$service' is not running (state=$container_state)"
docker compose "${compose_files[@]}" logs "$service"
return 1
fi

if [[ "$health_state" == 'unhealthy' ]]; then
echo "Service '$service' is unhealthy"
docker compose "${compose_files[@]}" logs "$service"
return 1
fi

if [[ "$health_state" == 'healthy' ]]; then
echo "Service '$service' is healthy"
return 0
fi

if docker compose "${compose_files[@]}" logs "$service" | grep -Eq "$ready_pattern"; then
echo "Service '$service' is ready"
return 0
fi

echo "Waiting '$service' to start up (attempt ${attempt}/${retries})"
sleep "$delay"
done

echo "Service '$service' did not become ready in time"
docker compose "${compose_files[@]}" logs "$service"
return 1
}

mapfile -t services_to_wait < <(
docker compose "${compose_files[@]}" ps --services --status running \
| grep -- '-service$' \
| sort
)

wait_for_mongo_primary

if (( ${#services_to_wait[@]} == 0 )); then
echo "No running -service containers found to wait for"
docker compose "${compose_files[@]}" ps
exit 1
fi

echo "Waiting for services: ${services_to_wait[*]}"

for service in "${services_to_wait[@]}"; do
wait_for_service "$service"
done
until docker compose -f docker-compose-ci.yml logs ddp-streamer-service | grep -q "NetworkBroker started successfully"; do
echo "Waiting 'ddp-streamer' to start up"
((c++)) && ((c==10)) && docker compose -f docker-compose-ci.yml logs ddp-streamer-service && exit 1
sleep 10
done;

- name: Remove unused Docker images
run: docker system prune -af
Expand All @@ -324,11 +215,10 @@ jobs:
IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }}
run: |
set -o xtrace
read -r -a compose_files_meteor <<< "$COMPOSE_FILES_METEOR"

npm run testapi || s=$?

docker compose "${compose_files_meteor[@]}" stop
docker compose -f ../../docker-compose-ci.yml stop

ls -la "$COVERAGE_DIR"
exit "${s:-0}"
Expand All @@ -341,11 +231,10 @@ jobs:
IS_EE: ${{ (inputs.release == 'ee' || inputs.release == 'fips') && 'true' || '' }}
run: |
set -o xtrace
read -r -a compose_files_meteor <<< "$COMPOSE_FILES_METEOR"

npm run testapi:livechat || s=$?

docker compose "${compose_files_meteor[@]}" stop
docker compose -f ../../docker-compose-ci.yml stop

ls -la "$COVERAGE_DIR"
exit "${s:-0}"
Expand Down Expand Up @@ -396,15 +285,11 @@ jobs:

- name: Show server logs if E2E test failed
if: failure()
run: |
read -r -a compose_files <<< "$COMPOSE_FILES"
docker compose "${compose_files[@]}" logs rocketchat authorization-service queue-worker-service ddp-streamer-service account-service presence-service omnichannel-transcript-service
run: docker compose -f docker-compose-ci.yml logs rocketchat authorization-service queue-worker-service ddp-streamer-service account-service presence-service omnichannel-transcript-service

- name: Show mongo logs if E2E test failed
if: failure()
run: |
read -r -a compose_files <<< "$COMPOSE_FILES"
docker compose "${compose_files[@]}" logs mongo
run: docker compose -f docker-compose-ci.yml logs mongo

- name: Store coverage
if: inputs.coverage == matrix.mongodb-version
Expand Down
Loading
Loading