From dfe9fd71023a1cc92b76e540ccc51ebdea94342f Mon Sep 17 00:00:00 2001 From: Nigel Gott <nigel@baserow.io> Date: Wed, 2 Feb 2022 17:16:02 +0000 Subject: [PATCH] Skip ci jobs where files weren't changed and the previous/same commit has a successful job run. --- .gitlab-ci.yml | 55 +++++++++++++----- .gitlab/ci_includes/jobs.yml | 109 +++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 14 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 00b615f04..2a8532026 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -89,6 +89,9 @@ stages: - publish variables: + ENABLE_JOB_SKIPPING: + value: "true" + description: "If set to false then tests and lints will be forced to run and not use previously cached results." ENABLE_COVERAGE: value: "true" description: "If set to false then tests will not generate coverage or testing reports used by gitlab to show nicer MRs." @@ -178,24 +181,29 @@ build-backend-image: DEV_IMAGE_NAME: $BACKEND_DEV_IMAGE_NAME DOCKERFILE_PATH: $BACKEND_DOCKERFILE_PATH -# If pipeline not triggered by tag and backend code has changed: -# - Runs the backend lint +# If pipeline not triggered by tag: +# - Runs the backend lint if changes to the backend, otherwise skips. backend-lint: extends: - .docker-image-test-stage + - .skippable-job + variables: + RUN_WHEN_CHANGES_MADE_IN: "backend/ premium/backend/" script: - docker run --rm $BACKEND_CI_DEV_IMAGE lint -# If pipeline not triggered by tag and backend code has changed: -# - Runs the backend startup check -# - Generates coverage db's and stores as artifact for later coverage merge and report +# If pipeline not triggered by tag: +# - Runs the backend startup check if changes to the backend, otherwise skips. backend-check-startup: extends: - .docker-image-test-stage + - .skippable-job services: - docker:20.10.12-dind - name: postgres:11.3 alias: db + variables: + RUN_WHEN_CHANGES_MADE_IN: "backend/ premium/backend/" script: - DB_IP=$(cat /etc/hosts | awk '{if ($2 == "db") print $1;}') - ping -w 2 $DB_IP @@ -208,12 +216,13 @@ backend-check-startup: --add-host="db:$DB_IP" \ $BACKEND_CI_DEV_IMAGE ci-check-startup; -# If pipeline not triggered by tag and backend code has changed: -# - Runs the backend tests (the first 1/3) +# If pipeline not triggered by tag: +# - Runs the backend tests (the first 1/3) if changes to the backend, otherwise skips. # - Generates coverage db's and stores as artifact for later coverage merge and report backend-test-group-1: extends: - .docker-image-test-stage + - .skippable-job services: - docker:20.10.12-dind - name: postgres:11.3 @@ -227,6 +236,8 @@ backend-test-group-1: POSTGRES_PASSWORD: baserow POSTGRES_DB: baserow PYTEST_SPLIT_GROUP: 1 + RUN_WHEN_CHANGES_MADE_IN: "backend/ premium/backend/" + DOWNLOAD_AND_UNPACK_ARTIFACTS_ON_SKIP: 'true' script: - MJML_IP=$(cat /etc/hosts | awk '{if ($2 == "mjml") print $1;}') - ping -w 2 $MJML_IP @@ -244,6 +255,17 @@ backend-test-group-1: $BACKEND_CI_DEV_IMAGE $TEST_TYPE; - docker cp baserow_backend_test_container:/baserow/backend/reports . - docker rm baserow_backend_test_container + - | + if [[ $PYTEST_SPLIT_GROUP = 1 ]]; then + docker run -e DATABASE_USER=baserow \ + -e DATABASE_NAME=baserow \ + -e DATABASE_HOST=db \ + -e DATABASE_PASSWORD=baserow \ + --rm \ + --add-host="db:$DB_IP" \ + --add-host="mjml:$MJML_IP" \ + $BACKEND_CI_DEV_IMAGE ci-check-startup; + fi artifacts: paths: - reports/ @@ -296,6 +318,7 @@ collect-backend-coverage: reports: cobertura: coverage.xml coverage: '/^TOTAL.+?(\d+\%)$/' + # If pipeline not triggered by tag: # - Build and store non-dev images in CI repo under the `ci-tested` tag so we know # those images have passed the tests. @@ -316,23 +339,27 @@ build-web-frontend-image: DEV_IMAGE_NAME: $WEBFRONTEND_DEV_IMAGE_NAME DOCKERFILE_PATH: $WEBFRONTEND_DOCKERFILE_PATH -# If pipeline not triggered by tag and web-frontend code has changed: -# - Runs eslint and stylelint -# - Stores a web-frontend_lint_success file in the cache so future pipelines can skip -# if no file changes. +# If pipeline not triggered by tag: +# - Runs eslint and stylelint if the web-frontend code has changed, otherwise skips. web-frontend-lint: extends: - .docker-image-test-stage + - .skippable-job + variables: + RUN_WHEN_CHANGES_MADE_IN: "web-frontend/ premium/web-frontend/" script: - docker run --rm $WEBFRONTEND_CI_DEV_IMAGE lint -# If pipeline not triggered by tag and web-frontend code has changed: -# - Runs the web-frontend tests +# If pipeline not triggered by tag: +# - Runs the web-frontend tests if the web-frontend has changed, otherwise skips. # - Generates coverage and testing reports -# - Stores the reports in the cache if successful web-frontend-test: extends: - .docker-image-test-stage + - .skippable-job + variables: + RUN_WHEN_CHANGES_MADE_IN: "web-frontend/ premium/web-frontend/" + DOWNLOAD_AND_UNPACK_ARTIFACTS_ON_SKIP: 'true' script: - mkdir reports/ -p - TEST_TYPE=$([[ "$ENABLE_COVERAGE" = "true" ]] && echo "ci-test" || echo "test") diff --git a/.gitlab/ci_includes/jobs.yml b/.gitlab/ci_includes/jobs.yml index 389ceb2fb..535b08a83 100644 --- a/.gitlab/ci_includes/jobs.yml +++ b/.gitlab/ci_includes/jobs.yml @@ -265,3 +265,112 @@ - docker:20.10.12-dind +.skippable-job: + before_script: + - | + CLEAR="\e[0m" + RED="\e[31m" + GREEN="\e[32m" + + echo -e "$GREEN =========== JOB SKIPPER =========== $CLEAR" + if [[ -z "$RUN_WHEN_CHANGES_MADE_IN" ]]; then + echo "Must provide RUN_WHEN_CHANGES_MADE_IN as a job variable" 2>&1 + exit 1 + fi + + if [[ "$ENABLE_JOB_SKIPPING" = "true" ]]; then + + exit_with_copied_artifacts_if_successful_job_for_commit(){ + COMMIT_HASH=$1 + JOB_NAME=$2 + URL="https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/repository/commits/$COMMIT_HASH/statuses?name=$JOB_NAME" + COMMIT_GITLAB_JOBS=$(curl --header "PRIVATE-TOKEN: $PROJECT_READ_ONLY_API_TOKEN" $URL) + + if [[ "$COMMIT_GITLAB_JOBS" ]]; then + echo -e "\e[0Ksection_start:`date +%s`:$COMMIT_HASH$JOB_NAME[collapsed=true]\r\e[0KRaw job status download for $JOB_NAME and $COMMIT_HASH" + echo "Got these job statuses: $COMMIT_GITLAB_JOBS" + JOB_ID=$(echo $COMMIT_GITLAB_JOBS| jq "[.[] | select(.status == \"success\")][0].id") + echo -e "\e[0Ksection_end:`date +%s`:$COMMIT_HASH$JOB_NAME\r\e[0K" + # Check if JOB_ID is an integer (POSIX compliant way) + + # Check if JOB_ID is an integer using bash magic. + if [ "$JOB_ID" -eq "$JOB_ID" ] 2> /dev/null; then + if [[ -n "$DOWNLOAD_AND_UNPACK_ARTIFACTS_ON_SKIP" ]] ; then + exit_code=0 + curl --fail --location --output artifacts.zip \ + --header "PRIVATE-TOKEN: $PROJECT_READ_ONLY_API_TOKEN" \ + "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/jobs/$JOB_ID/artifacts" \ + || exit_code=$?; + + if [ ${exit_code} -ne 0 ]; then + echo -e "$RED Failed to get artifacts from successful run $JOB_ID $CLEAR" + else + unzip artifacts.zip || exit_code=$? + if [ ${exit_code} -ne 0 ]; then + echo -e "$RED Failed to unzip artifacts $CLEAR" + else + # Echo a stdout report if found so gitlab's coverage regex which + # searches stdout to find the overall coverage is correct even for + # skipped jobs. + if [[ -f "reports/stdout.txt" ]]; then + cat reports/stdout.txt; + fi + echo -e "$GREEN Skipping $JOB_NAME as previous successful run for $COMMIT_HASH and it's artifacts were found. $CLEAR" + exit 0; + fi + fi + + else + echo -e "$GREEN Skipping $JOB_NAME as previous successful build for $COMMIT_HASH was found. $CLEAR". + exit 0; + fi + else + echo "Failed to find successful run of $JOB_NAME in job statuses from gitlab for commit $COMMIT_HASH." + fi + else + echo -e "$RED Failed to query gitlab for jobs $CLEAR"; + fi + } + + echo "Checking if we can skip immediately if this commit already has a successful job run..." + exit_with_copied_artifacts_if_successful_job_for_commit $CI_COMMIT_SHA $CI_JOB_NAME + echo "Can't immediately skip as there was no successful previous job for this commit, checking changes..." + + CHANGED_FILES=$(git diff --name-only --diff-filter=ADMR @~..@) + grep_exit_code=0 + found_changes=0 + for SEARCH_PATTERN in $RUN_WHEN_CHANGES_MADE_IN; do + echo $CHANGED_FILES | grep -q $SEARCH_PATTERN || grep_exit_code=$?; + if [ ${grep_exit_code} -eq 0 ]; then + echo -e "Found changes matching $GREEN $SEARCH_PATTERN $CLEAR in:" + echo $CHANGED_FILES + echo -e "$GREEN Running job normally without skipping due to the changes. $CLEAR" + found_changes=1 + break + fi + done + + if [ ${found_changes} -eq 0 ]; then + echo "No git diff changes found matching $RUN_WHEN_CHANGES_MADE_IN." + echo "Checking for previous commits job..." + SECOND_PARENT_COMMIT=$(git rev-list -1 --merges ${CI_COMMIT_SHA}~1..${CI_COMMIT_SHA}) + if [[ -z "$SECOND_PARENT_COMMIT" ]] ; then + # If there is no second parent commit then there is only one parent commit + # and so we can safely check to see if that parent commit has a successful + # job run as this commit does not change any relavent files. + PREVIOUS_COMMIT_SHA=$(git rev-parse HEAD~1) + echo "Found single previous commit $PREVIOUS_COMMIT_SHA, checking for job.." + exit_with_copied_artifacts_if_successful_job_for_commit $PREVIOUS_COMMIT_SHA $CI_JOB_NAME + echo -e "$GREEN Running job without skipping as successful run for previous or this commit not found $CLEAR" + else + # There are more than one parent commits meaning we should re-run this job + # as this commit is a merge commit with multiple parents, so we can't safely + # skip this job. + echo -e "$GREEN Running full job as this is a merge commit. $CLEAR" + fi + fi + else + echo -e "$GREEN Force running job regardless of previous runs. $CLEAR" + fi + + echo -e "$GREEN ================================ $CLEAR"