Skip to content

Commit 8434b33

Browse files
🚀 PR: Migrate CI from Travis CI to GitHub Actions (#5070)
* Remove Travis CI configuration and update documentation to reflect migration to GitHub Actions for CI/CD. Adjust Dockerfile and deployment scripts to support new environment variables and improve deployment processes. Update karma configuration to support multiple CI providers. (#5060) * Refactor GitHub Actions workflow: Replace manual Docker Hub authentication with docker/login-action for improved security and simplicity. Ensure credentials are only used when available, enhancing the CI/CD process. (#5061) * Update GitHub Actions workflow: Enhance Docker Hub authentication by using environment variables for credentials, ensuring authentication is only attempted when secrets are configured. Add CODECOV_TOKEN for Codecov uploads, improving coverage reporting. (#5062) * Update GitHub Actions workflow: Enable Codecov uploads using GitHub OIDC for authentication, eliminating the need for CODECOV_TOKEN. This enhances security and simplifies the coverage reporting process. (#5063)
1 parent 48ab22f commit 8434b33

11 files changed

Lines changed: 730 additions & 397 deletions

File tree

.dockerignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ Dockerfile*
5959
# CI/CD
6060
.github/
6161
.gitlab-ci.yml
62-
.travis.yml
6362

6463
# Testing
6564
.pytest_cache/

.github/CONTRIBUTING.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Our central development branch is development. Coding is done on feature branche
6868

6969
- On your GitHub fork, select your branch and click “New pull request”. Select “master” as the base branch and your branch in the “compare” dropdown.
7070
If the code is mergeable (you get a message saying “Able to merge”), go ahead and create the pull request.
71-
- Check back after some time to see if the Travis checks have passed, if not you should click on “Details” link on your PR thread at the right of “The Travis CI build failed”, which will take you to the dashboard for your PR. You will see what failed / stalled, and will need to resolve them.
71+
- Check back after a few minutes to see if the GitHub Actions workflow checks have completed. If they have not passed, click the “Details” link next to the failed check in your PR checks section to open the Actions run page, inspect logs, and fix the failing step before pushing another commit.
7272
- If your checks have passed, your PR will be assigned a reviewer who will review your code and provide comments. Please address each review comment by pushing new commits to the same branch (the PR will automatically update, so you don’t need to submit a new one). Once you are done, comment below each review comment marking it as “Done”. Feel free to use the thread to have a discussion about comments that you don’t understand completely or don’t agree with.
7373

7474
- Once all comments are addressed, the maintainer will approve the PR.
@@ -84,4 +84,13 @@ If the code is mergeable (you get a message saying “Able to merge”), go ahea
8484
- For further query regarding rebasing, visit https://github.com/todotxt/todo.txt-android/wiki/Squash-All-Commits-Related-to-a-Single-Issue-into-a-Single-Commit
8585
- Once rebasing is done, the reviewer will approve and merge the PR.
8686

87+
### GitHub Actions deployment configuration
88+
89+
The CI/CD workflow in `.github/workflows/continuous-integration-and-deployment.yml` expects deployment configuration to be stored in GitHub Environments (`staging` and `production`) using descriptive secret names:
90+
91+
- Environment secrets: `AWS_ACCOUNT_ID`, `AWS_REGION`, `AWS_OIDC_DEPLOYMENT_ROLE_ARN`, `JUMPBOX_INSTANCE`, `DEPLOYMENT_INSTANCE_HOST`, `DEPLOYMENT_SSH_PRIVATE_KEY`, `DEPLOYMENT_SSH_KNOWN_HOSTS`
92+
- Repository secrets (optional for authenticated Docker Hub access): `DOCKER_USERNAME`, `DOCKER_PASSWORD`
93+
94+
The deployment job uses AWS OIDC role assumption and does not require long-lived AWS access keys in GitHub secrets.
95+
8796
Congratulations, you have successfully contributed to Project EvalAI!
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
name: Continuous Integration and Deployment
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
inputs:
8+
deployment_environment_name:
9+
description: "Deployment environment to target (staging or production)"
10+
required: true
11+
type: choice
12+
options:
13+
- staging
14+
- production
15+
default: staging
16+
run_deployment_in_dry_mode:
17+
description: "Validate deployment access without updating remote services"
18+
required: true
19+
type: boolean
20+
default: true
21+
22+
concurrency:
23+
group: continuous-integration-and-deployment-${{ github.ref }}
24+
cancel-in-progress: true
25+
26+
env:
27+
PYTHON_RUNTIME_VERSION: "3.9.21"
28+
AWS_COMMAND_LINE_INTERFACE_VERSION: "1.18.66"
29+
DISPLAY_SERVER_ADDRESS: ":99.0"
30+
CHROME_BROWSER_BINARY: "chromium-browser"
31+
WORKER_DOCKER_COMPOSE_PROFILES: "--profile worker_py3_7 --profile worker_py3_8 --profile worker_py3_9"
32+
DOCKER_COMPOSE_BUILD_ARGUMENTS: "--build-arg PIP_NO_CACHE_DIR=1"
33+
34+
jobs:
35+
build_test_quality:
36+
name: Build Test and Quality Checks
37+
runs-on: ubuntu-latest
38+
permissions:
39+
contents: read
40+
id-token: write # Codecov OIDC uploads (codecov-action use_oidc)
41+
42+
steps:
43+
- name: Checkout repository
44+
uses: actions/checkout@v4
45+
with:
46+
fetch-depth: 0
47+
48+
- name: Set up Python runtime
49+
uses: actions/setup-python@v5
50+
with:
51+
python-version: ${{ env.PYTHON_RUNTIME_VERSION }}
52+
cache: pip
53+
54+
- name: Validate Docker Compose availability
55+
run: |
56+
docker --version
57+
docker compose version
58+
59+
- name: Install shared command-line dependencies
60+
run: |
61+
pip install --upgrade pip
62+
pip install awscli=="${AWS_COMMAND_LINE_INTERFACE_VERSION}"
63+
sudo rm -f /etc/boto.cfg
64+
mkdir -p "$HOME/.config/pip"
65+
{
66+
echo "[build_ext]"
67+
echo "parallel = 1"
68+
} > "$HOME/.config/pip/pip.conf"
69+
70+
- name: Configure CI runtime limits
71+
run: |
72+
ulimit -u 16384 || true
73+
ulimit -n 4096 || true
74+
75+
# Optional: set DOCKER_USERNAME + DOCKER_PASSWORD (access token) repo secrets for higher Docker Hub pull limits.
76+
# secrets cannot be used in step `if:`; gate on event here, then check credentials in the shell.
77+
- name: Log in to Docker Hub
78+
if: github.event_name != 'pull_request'
79+
env:
80+
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
81+
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
82+
run: |
83+
if [ -n "${DOCKER_USERNAME}" ] && [ -n "${DOCKER_PASSWORD}" ]; then
84+
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
85+
else
86+
echo "Skipping Docker Hub authentication (secrets not configured)."
87+
fi
88+
89+
- name: Set up Docker Buildx
90+
uses: docker/setup-buildx-action@v3
91+
92+
- name: Build Docker images for CI
93+
env:
94+
COMPOSE_BAKE: true
95+
DOCKER_BUILDKIT: 1
96+
run: |
97+
docker compose $WORKER_DOCKER_COMPOSE_PROFILES build $DOCKER_COMPOSE_BUILD_ARGUMENTS
98+
99+
- name: Run frontend build and tests
100+
env:
101+
CHROME_BIN: ${{ env.CHROME_BROWSER_BINARY }}
102+
DISPLAY: ${{ env.DISPLAY_SERVER_ADDRESS }}
103+
run: |
104+
docker compose run nodejs bash -c '
105+
set -e
106+
echo "=== Installing dependencies ==="
107+
npm install
108+
export SASS_SILENCE_DEPRECATIONS=legacy-js-api,import
109+
echo "=== Building frontend (dev) ==="
110+
gulp dev
111+
echo "=== Running frontend tests ==="
112+
mkdir -p coverage/frontend
113+
karma_result=0
114+
timeout 300 karma start --single-run --reporters=brief,junit,coverage || karma_result=$?
115+
pkill -f chrome 2>/dev/null || true
116+
pkill -f chromium 2>/dev/null || true
117+
sleep 3
118+
junit_status=2
119+
junit_file=$(find coverage/frontend -name "TEST-frontend.xml" -type f 2>/dev/null | head -n 1 || true)
120+
if [ -n "$junit_file" ]; then
121+
if grep -Eq "failures=\"0\"" "$junit_file" && grep -Eq "errors=\"0\"" "$junit_file"; then
122+
junit_status=0
123+
else
124+
junit_status=1
125+
fi
126+
fi
127+
if [ $junit_status -eq 0 ]; then
128+
echo "=== Frontend tests passed ==="
129+
elif [ $junit_status -eq 1 ]; then
130+
echo "=== Frontend tests failed ==="
131+
exit 1
132+
elif [ $karma_result -eq 124 ] && [ -f coverage/frontend/lcov.info ]; then
133+
echo "=== Frontend tests passed (karma timed out after completion) ==="
134+
elif [ $karma_result -eq 0 ]; then
135+
echo "=== Frontend tests passed ==="
136+
else
137+
echo "=== Frontend tests failed ==="
138+
exit 1
139+
fi
140+
echo "=== Building frontend (staging) ==="
141+
gulp staging
142+
echo "=== Frontend build complete ==="
143+
'
144+
145+
- name: Run backend tests
146+
run: |
147+
docker compose run -e DJANGO_SETTINGS_MODULE=settings.test django bash -c '
148+
set -e
149+
echo "=== Flushing database ==="
150+
python manage.py flush --noinput
151+
echo "=== Running backend tests with coverage ==="
152+
pytest --cov . --cov-config .coveragerc --cov-report=xml:coverage-backend.xml --junitxml=junit-backend.xml -o junit_family=legacy
153+
echo "=== Backend tests passed ==="
154+
'
155+
156+
- name: Run code quality checks
157+
run: |
158+
docker compose run -e DJANGO_SETTINGS_MODULE=settings.dev -e VERBOSE=1 django bash -c '
159+
set -e
160+
echo "=== Installing code quality tools ==="
161+
pip install black==24.8.0 flake8==3.8.2 pylint==3.3.6 isort==5.12.0
162+
echo "=== Running black check ==="
163+
black --check --diff ./
164+
echo "=== Running isort check ==="
165+
isort --check-only --diff --profile=black ./
166+
echo "=== Running flake8 check ==="
167+
flake8 --config=.flake8 ./
168+
echo "=== Running pylint check ==="
169+
pylint --rcfile=.pylintrc --output-format=colorized --score=y --fail-under=7.5 ./
170+
echo "=== All code quality checks passed ==="
171+
'
172+
173+
- name: Validate Django migrations for pull requests
174+
if: github.event_name == 'pull_request'
175+
run: |
176+
docker compose run -e DJANGO_SETTINGS_MODULE=settings.dev django python manage.py makemigrations --check --dry-run
177+
178+
- name: Upload backend and frontend test reports
179+
uses: actions/upload-artifact@v4
180+
with:
181+
name: junit-test-reports
182+
path: |
183+
junit-backend.xml
184+
coverage/frontend/TEST-frontend.xml
185+
if-no-files-found: warn
186+
187+
# Authenticate uploads with GitHub OIDC (no CODECOV_TOKEN secret). Requires
188+
# codecov-action v4.2+. Fork PRs: GitHub may not mint id-token; public repos may
189+
# still get tokenless uploads per Codecov branch naming — see codecov/codecov-action#1489.
190+
- name: Upload backend coverage to Codecov
191+
uses: codecov/codecov-action@v4
192+
with:
193+
use_oidc: true
194+
files: coverage-backend.xml
195+
flags: backend
196+
fail_ci_if_error: true
197+
verbose: true
198+
199+
- name: Upload frontend coverage to Codecov
200+
uses: codecov/codecov-action@v4
201+
with:
202+
use_oidc: true
203+
files: coverage/frontend/lcov.info
204+
flags: frontend
205+
fail_ci_if_error: true
206+
verbose: true
207+
208+
package_and_deploy_services:
209+
name: Package and Deploy Services
210+
runs-on: ubuntu-latest
211+
needs:
212+
- build_test_quality
213+
permissions:
214+
contents: read
215+
id-token: write
216+
if: >
217+
(github.event_name == 'push' && (github.ref_name == 'staging' || github.ref_name == 'production')) ||
218+
(github.event_name == 'workflow_dispatch' && (github.event.inputs.deployment_environment_name == 'staging' || github.event.inputs.deployment_environment_name == 'production'))
219+
environment: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.deployment_environment_name || github.ref_name }}
220+
221+
steps:
222+
- name: Checkout repository
223+
uses: actions/checkout@v4
224+
225+
- name: Determine deployment context
226+
id: deployment_context
227+
run: |
228+
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
229+
echo "deployment_environment_name=${{ github.event.inputs.deployment_environment_name }}" >> "$GITHUB_OUTPUT"
230+
echo "run_deployment_in_dry_mode=${{ github.event.inputs.run_deployment_in_dry_mode }}" >> "$GITHUB_OUTPUT"
231+
else
232+
echo "deployment_environment_name=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
233+
echo "run_deployment_in_dry_mode=false" >> "$GITHUB_OUTPUT"
234+
fi
235+
236+
- name: Validate required deployment configuration
237+
env:
238+
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
239+
AWS_REGION: ${{ secrets.AWS_REGION }}
240+
AWS_OIDC_DEPLOYMENT_ROLE_ARN: ${{ secrets.AWS_OIDC_DEPLOYMENT_ROLE_ARN }}
241+
JUMPBOX_INSTANCE: ${{ secrets.JUMPBOX_INSTANCE }}
242+
DEPLOYMENT_INSTANCE_HOST: ${{ secrets.DEPLOYMENT_INSTANCE_HOST }}
243+
DEPLOYMENT_SSH_PRIVATE_KEY: ${{ secrets.DEPLOYMENT_SSH_PRIVATE_KEY }}
244+
DEPLOYMENT_SSH_KNOWN_HOSTS: ${{ secrets.DEPLOYMENT_SSH_KNOWN_HOSTS }}
245+
run: |
246+
required_variable_names=(
247+
AWS_ACCOUNT_ID
248+
AWS_REGION
249+
AWS_OIDC_DEPLOYMENT_ROLE_ARN
250+
JUMPBOX_INSTANCE
251+
DEPLOYMENT_INSTANCE_HOST
252+
DEPLOYMENT_SSH_PRIVATE_KEY
253+
DEPLOYMENT_SSH_KNOWN_HOSTS
254+
)
255+
256+
for required_variable_name in "${required_variable_names[@]}"; do
257+
if [ -z "${!required_variable_name}" ]; then
258+
echo "Required configuration '${required_variable_name}' is missing."
259+
exit 1
260+
fi
261+
done
262+
263+
- name: Configure AWS credentials from OIDC role
264+
uses: aws-actions/configure-aws-credentials@v4
265+
with:
266+
role-to-assume: ${{ secrets.AWS_OIDC_DEPLOYMENT_ROLE_ARN }}
267+
aws-region: ${{ secrets.AWS_REGION }}
268+
269+
- name: Set up Python runtime
270+
uses: actions/setup-python@v5
271+
with:
272+
python-version: ${{ env.PYTHON_RUNTIME_VERSION }}
273+
cache: pip
274+
275+
- name: Install deployment dependencies
276+
run: |
277+
pip install --upgrade pip
278+
pip install awscli=="${AWS_COMMAND_LINE_INTERFACE_VERSION}"
279+
280+
- name: Set up Docker Buildx
281+
uses: docker/setup-buildx-action@v3
282+
283+
- name: Log in to Docker Hub
284+
env:
285+
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
286+
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
287+
run: |
288+
if [ -n "${DOCKER_USERNAME}" ] && [ -n "${DOCKER_PASSWORD}" ]; then
289+
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
290+
else
291+
echo "Skipping Docker Hub authentication (secrets not configured)."
292+
fi
293+
294+
- name: Prepare SSH key and known hosts
295+
id: ssh_materials
296+
env:
297+
DEPLOYMENT_SSH_PRIVATE_KEY: ${{ secrets.DEPLOYMENT_SSH_PRIVATE_KEY }}
298+
DEPLOYMENT_SSH_KNOWN_HOSTS: ${{ secrets.DEPLOYMENT_SSH_KNOWN_HOSTS }}
299+
run: |
300+
ssh_private_key_path="$RUNNER_TEMP/evalai_deployment_key"
301+
known_hosts_path="$RUNNER_TEMP/evalai_known_hosts"
302+
303+
printf '%s\n' "$DEPLOYMENT_SSH_PRIVATE_KEY" > "$ssh_private_key_path"
304+
chmod 600 "$ssh_private_key_path"
305+
306+
printf '%s\n' "$DEPLOYMENT_SSH_KNOWN_HOSTS" > "$known_hosts_path"
307+
chmod 600 "$known_hosts_path"
308+
309+
echo "ssh_private_key_path=${ssh_private_key_path}" >> "$GITHUB_OUTPUT"
310+
echo "known_hosts_path=${known_hosts_path}" >> "$GITHUB_OUTPUT"
311+
312+
- name: Package and deploy services
313+
env:
314+
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
315+
AWS_REGION: ${{ secrets.AWS_REGION }}
316+
DEPLOYMENT_BRANCH_NAME: ${{ steps.deployment_context.outputs.deployment_environment_name }}
317+
CI_EVENT_NAME: ${{ github.event_name }}
318+
IS_PULL_REQUEST_BUILD: "false"
319+
DRY_RUN_DEPLOYMENT: ${{ steps.deployment_context.outputs.run_deployment_in_dry_mode }}
320+
JUMPBOX_INSTANCE: ${{ secrets.JUMPBOX_INSTANCE }}
321+
TARGET_INSTANCE_HOST: ${{ secrets.DEPLOYMENT_INSTANCE_HOST }}
322+
SSH_PRIVATE_KEY_PATH: ${{ steps.ssh_materials.outputs.ssh_private_key_path }}
323+
DEPLOY_SSH_KNOWN_HOSTS_PATH: ${{ steps.ssh_materials.outputs.known_hosts_path }}
324+
COMPOSE_BAKE: true
325+
DOCKER_BUILDKIT: 1
326+
run: |
327+
bash ./scripts/deployment/push.sh
328+
bash ./scripts/deployment/deploy.sh auto_deploy

0 commit comments

Comments
 (0)