Commit 2655f613 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch 'ee-improve-review-apps-jobs-structure' into 'master'

[EE] Organize better Review Apps and QA jobs

See merge request gitlab-org/gitlab-ee!11445
parents bbeafbae a6e7a87c
...@@ -34,6 +34,8 @@ stages: ...@@ -34,6 +34,8 @@ stages:
- prepare - prepare
- merge - merge
- test - test
- review
- qa
- post-test - post-test
- pages - pages
- post-cleanup - post-cleanup
......
...@@ -9,7 +9,7 @@ cloud-native-image: ...@@ -9,7 +9,7 @@ cloud-native-image:
cache: {} cache: {}
when: manual when: manual
script: script:
- gem install gitlab --no-document - install_gitlab_gem
- CNG_PROJECT_PATH="gitlab-org/build/CNG" BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN ./scripts/trigger-build cng - CNG_PROJECT_PATH="gitlab-org/build/CNG" BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN ./scripts/trigger-build cng
only: only:
- tags@gitlab-org/gitlab-ce - tags@gitlab-org/gitlab-ce
......
...@@ -38,6 +38,10 @@ gitlab:assets:compile: ...@@ -38,6 +38,10 @@ gitlab:assets:compile:
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
- time scripts/build_assets_image - time scripts/build_assets_image
- scripts/clean-old-cached-assets - scripts/clean-old-cached-assets
# Play dependent manual jobs
- install_api_client_dependencies_with_apt
- play_job "review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
- play_job "schedule:review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
artifacts: artifacts:
name: webpack-report name: webpack-report
expire_in: 31d expire_in: 31d
......
package-and-qa: package-and-qa:
image: ruby:2.5-alpine image: ruby:2.5-alpine
stage: test stage: qa
when: manual
before_script: [] before_script: []
dependencies: [] dependencies: []
cache: {} cache: {}
variables: variables:
GIT_DEPTH: "1" GIT_DEPTH: "1"
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
retry: 0 retry: 0
script: script:
- apk add --update openssl curl jq - source scripts/utils.sh
- gem install gitlab --no-document - install_gitlab_gem
- source ./scripts/review_apps/review-apps.sh
- wait_for_job_to_be_done "gitlab:assets:compile"
- ./scripts/trigger-build omnibus - ./scripts/trigger-build omnibus
when: manual
only: only:
- /.+/@gitlab-org/gitlab-ce - /.+/@gitlab-org/gitlab-ce
- /.+/@gitlab-org/gitlab-ee - /.+/@gitlab-org/gitlab-ee
...@@ -26,12 +26,10 @@ ...@@ -26,12 +26,10 @@
extends: .dedicated-runner extends: .dedicated-runner
<<: *review-only <<: *review-only
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
stage: test
cache: {} cache: {}
dependencies: [] dependencies: []
environment: &review-environment before_script:
name: review/${CI_COMMIT_REF_NAME} - source scripts/utils.sh
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
.review-docker: &review-docker .review-docker: &review-docker
<<: *review-base <<: *review-base
...@@ -42,18 +40,13 @@ ...@@ -42,18 +40,13 @@
- gitlab-org - gitlab-org
- docker - docker
variables: &review-docker-variables variables: &review-docker-variables
GIT_DEPTH: "1"
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375 DOCKER_HOST: tcp://docker:2375
LATEST_QA_IMAGE: "gitlab/${CI_PROJECT_NAME}-qa:nightly" LATEST_QA_IMAGE: "gitlab/${CI_PROJECT_NAME}-qa:nightly"
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}" QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}"
before_script: []
build-qa-image: build-qa-image:
<<: *review-docker <<: *review-docker
variables:
<<: *review-docker-variables
GIT_DEPTH: "20"
stage: prepare stage: prepare
script: script:
- time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} ./qa/ - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} ./qa/
...@@ -63,16 +56,14 @@ build-qa-image: ...@@ -63,16 +56,14 @@ build-qa-image:
.review-build-cng-base: &review-build-cng-base .review-build-cng-base: &review-build-cng-base
image: ruby:2.5-alpine image: ruby:2.5-alpine
stage: test stage: test
before_script: [] when: manual
before_script:
- source scripts/utils.sh
- install_api_client_dependencies_with_apk
- install_gitlab_gem
dependencies: [] dependencies: []
cache: {} cache: {}
variables:
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
script: script:
- apk add --update openssl curl jq
- gem install gitlab --no-document
- source ./scripts/review_apps/review-apps.sh
- wait_for_job_to_be_done "gitlab:assets:compile"
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
review-build-cng: review-build-cng:
...@@ -85,26 +76,32 @@ schedule:review-build-cng: ...@@ -85,26 +76,32 @@ schedule:review-build-cng:
.review-deploy-base: &review-deploy-base .review-deploy-base: &review-deploy-base
<<: *review-base <<: *review-base
stage: review
retry: 2 retry: 2
allow_failure: true allow_failure: true
variables: variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}" HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}" DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "master" GITLAB_HELM_CHART_REF: "master"
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}" environment: &review-environment
environment: name: review/${CI_COMMIT_REF_NAME}
<<: *review-environment url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop on_stop: review-stop
before_script: before_script:
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION) - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- export GITALY_VERSION=$(<GITALY_SERVER_VERSION) - export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION) - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
- apk update && apk add jq - echo "${CI_ENVIRONMENT_URL}" > review_app_url.txt
- gem install gitlab --no-document - source scripts/utils.sh
- source ./scripts/review_apps/review-apps.sh - install_api_client_dependencies_with_apk
- source scripts/review_apps/review-apps.sh
script: script:
- wait_for_job_to_be_done "review-build-cng"
- perform_review_app_deployment - perform_review_app_deployment
artifacts:
paths:
- review_app_url.txt
expire_in: 2 days
when: always
review-deploy: review-deploy:
<<: *review-deploy-base <<: *review-deploy-base
...@@ -113,15 +110,29 @@ schedule:review-deploy: ...@@ -113,15 +110,29 @@ schedule:review-deploy:
<<: *review-deploy-base <<: *review-deploy-base
<<: *review-schedules-only <<: *review-schedules-only
script: script:
- wait_for_job_to_be_done "schedule:review-build-cng"
- perform_review_app_deployment - perform_review_app_deployment
review-stop:
<<: *review-base
stage: review
when: manual
allow_failure: true
variables:
GIT_DEPTH: "1"
environment:
<<: *review-environment
action: stop
script:
- source scripts/review_apps/review-apps.sh
- delete
- cleanup
.review-qa-base: &review-qa-base .review-qa-base: &review-qa-base
<<: *review-docker <<: *review-docker
stage: qa
allow_failure: true allow_failure: true
variables: variables:
<<: *review-docker-variables <<: *review-docker-variables
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa" QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
QA_CAN_TEST_GIT_PROTOCOL_V2: "false" QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
GITLAB_USERNAME: "root" GITLAB_USERNAME: "root"
...@@ -131,40 +142,45 @@ schedule:review-deploy: ...@@ -131,40 +142,45 @@ schedule:review-deploy:
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}" GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}" EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
QA_DEBUG: "true" QA_DEBUG: "true"
dependencies:
- review-deploy
artifacts: artifacts:
paths: paths:
- ./qa/gitlab-qa-run-* - ./qa/gitlab-qa-run-*
expire_in: 7 days expire_in: 7 days
when: always when: always
before_script: before_script:
- echo "${QA_IMAGE}" - export CI_ENVIRONMENT_URL="$(cat review_app_url.txt)"
- echo "${CI_ENVIRONMENT_URL}" - echo "${CI_ENVIRONMENT_URL}"
- apk update && apk add curl jq - echo "${QA_IMAGE}"
- source ./scripts/review_apps/review-apps.sh - source scripts/utils.sh
- install_api_client_dependencies_with_apk
- gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}} - gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
review-qa-smoke: review-qa-smoke:
<<: *review-qa-base <<: *review-qa-base
retry: 2 retry: 2
script: script:
- wait_for_job_to_be_done "review-deploy"
- gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
review-qa-all: review-qa-all:
<<: *review-qa-base <<: *review-qa-base
when: manual
script: script:
- wait_for_job_to_be_done "review-deploy"
- gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
when: manual
.review-performance-base: &review-performance-base .review-performance-base: &review-performance-base
<<: *review-qa-base <<: *review-qa-base
script: stage: qa
- wait_for_job_to_be_done "review-deploy" before_script:
- export CI_ENVIRONMENT_URL="$(cat review_app_url.txt)"
- echo "${CI_ENVIRONMENT_URL}"
- mkdir -p gitlab-exporter - mkdir -p gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir sitespeed-results - mkdir -p sitespeed-results
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL" script:
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "${CI_ENVIRONMENT_URL}"
after_script:
- mv sitespeed-results/data/performance.json performance.json - mv sitespeed-results/data/performance.json performance.json
artifacts: artifacts:
paths: paths:
...@@ -175,42 +191,27 @@ review-qa-all: ...@@ -175,42 +191,27 @@ review-qa-all:
review-performance: review-performance:
<<: *review-performance-base <<: *review-performance-base
review-stop: schedule:review-performance:
<<: *review-base <<: *review-performance-base
extends: .single-script-job-dedicated-runner <<: *review-schedules-only
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base dependencies:
allow_failure: true - schedule:review-deploy
variables:
SCRIPT_NAME: "review_apps/review-apps.sh"
when: manual
environment:
<<: *review-environment
action: stop
script:
- source $(basename "${SCRIPT_NAME}")
- delete
- cleanup
schedule:review-cleanup: schedule:review-cleanup:
<<: *review-base <<: *review-base
<<: *review-schedules-only <<: *review-schedules-only
stage: build stage: review
allow_failure: true allow_failure: true
variables: variables:
GIT_DEPTH: "1" GIT_DEPTH: "1"
environment: environment:
name: review/auto-cleanup name: review/auto-cleanup
before_script: before_script:
- gem install gitlab --no-document - source scripts/utils.sh
- install_gitlab_gem
script: script:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb - ruby -rrubygems scripts/review_apps/automated_cleanup.rb
schedule:review-performance:
<<: *review-performance-base
<<: *review-schedules-only
script:
- wait_for_job_to_be_done "schedule:review-deploy"
danger-review: danger-review:
extends: .dedicated-pull-cache-job extends: .dedicated-pull-cache-job
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
......
[[ "$TRACE" ]] && set -x [[ "$TRACE" ]] && set -x
export TILLER_NAMESPACE="$KUBE_NAMESPACE" export TILLER_NAMESPACE="$KUBE_NAMESPACE"
function echoerr() {
local header="${2}"
if [ -n "${header}" ]; then
printf "\n\033[0;31m** %s **\n\033[0m" "${1}" >&2;
else
printf "\033[0;31m%s\n\033[0m" "${1}" >&2;
fi
}
function echoinfo() {
local header="${2}"
if [ -n "${header}" ]; then
printf "\n\033[0;33m** %s **\n\033[0m" "${1}" >&2;
else
printf "\033[0;33m%s\n\033[0m" "${1}" >&2;
fi
}
function deployExists() { function deployExists() {
local namespace="${1}" local namespace="${1}"
local deploy="${2}" local deploy="${2}"
...@@ -328,80 +308,3 @@ function add_license() { ...@@ -328,80 +308,3 @@ function add_license() {
puts "License added"; puts "License added";
' '
} }
function get_job_id() {
local job_name="${1}"
local query_string="${2:+&${2}}"
local max_page=3
local page=1
while true; do
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
echoinfo "GET ${url}"
local job_id
job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
[[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
let "page++"
done
if [[ "${job_id}" == "" ]]; then
echoerr "The '${job_name}' job ID couldn't be retrieved!"
else
echoinfo "The '${job_name}' job ID is ${job_id}"
echo "${job_id}"
fi
}
function play_job() {
local job_name="${1}"
local job_id
job_id=$(get_job_id "${job_name}" "scope=manual");
if [ -z "${job_id}" ]; then return; fi
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
echoinfo "POST ${url}"
local job_url
job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".web_url")
echoinfo "Manual job '${job_name}' started at: ${job_url}"
}
function wait_for_job_to_be_done() {
local job_name="${1}"
local query_string="${2}"
local job_id
job_id=$(get_job_id "${job_name}" "${query_string}")
if [ -z "${job_id}" ]; then return; fi
echoinfo "Waiting for the '${job_name}' job to finish..."
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}"
echoinfo "GET ${url}"
# In case the job hasn't finished yet. Keep trying until the job times out.
local interval=30
local elapsed_seconds=0
while true; do
local job_status
job_status=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".status" | sed -e s/\"//g)
[[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break
printf "."
let "elapsed_seconds+=interval"
sleep ${interval}
done
local elapsed_minutes=$((elapsed_seconds / 60))
echoinfo "Waited '${job_name}' for ${elapsed_minutes} minutes."
if [[ "${job_status}" == "failed" ]]; then
echoerr "The '${job_name}' failed."
elif [[ "${job_status}" == "manual" ]]; then
echoinfo "The '${job_name}' is manual."
else
echoinfo "The '${job_name}' passed."
fi
}
retry() { function retry() {
if eval "$@"; then if eval "$@"; then
return 0 return 0
fi fi
...@@ -13,15 +13,15 @@ retry() { ...@@ -13,15 +13,15 @@ retry() {
return 1 return 1
} }
setup_db_user_only() { function setup_db_user_only() {
if [ "$GITLAB_DATABASE" = "postgresql" ]; then if [ "$GITLAB_DATABASE" = "postgresql" ]; then
. scripts/create_postgres_user.sh source scripts/create_postgres_user.sh
else else
. scripts/create_mysql_user.sh source scripts/create_mysql_user.sh
fi fi
} }
setup_db() { function setup_db() {
setup_db_user_only setup_db_user_only
bundle exec rake db:drop db:create db:schema:load db:migrate bundle exec rake db:drop db:create db:schema:load db:migrate
...@@ -33,3 +33,129 @@ setup_db() { ...@@ -33,3 +33,129 @@ setup_db() {
# EE-only # EE-only
bundle exec rake geo:db:drop geo:db:create geo:db:schema:load geo:db:migrate bundle exec rake geo:db:drop geo:db:create geo:db:schema:load geo:db:migrate
} }
function install_api_client_dependencies_with_apk() {
apk add --update openssl curl jq
}
function install_api_client_dependencies_with_apt() {
apt update && apt install jq -y
}
function install_gitlab_gem() {
gem install gitlab --no-document
}
function echoerr() {
local header="${2}"
if [ -n "${header}" ]; then
printf "\n\033[0;31m** %s **\n\033[0m" "${1}" >&2;
else
printf "\033[0;31m%s\n\033[0m" "${1}" >&2;
fi
}
function echoinfo() {
local header="${2}"
if [ -n "${header}" ]; then
printf "\n\033[0;33m** %s **\n\033[0m" "${1}" >&2;
else
printf "\033[0;33m%s\n\033[0m" "${1}" >&2;
fi
}
function get_job_id() {
local job_name="${1}"
local query_string="${2:+&${2}}"
local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
if [ -z "${api_token}" ]; then
echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
return
fi
local max_page=3
local page=1
while true; do
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
echoinfo "GET ${url}"
local job_id
job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
[[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
let "page++"
done
if [[ "${job_id}" == "" ]]; then
echoerr "The '${job_name}' job ID couldn't be retrieved!"
else
echoinfo "The '${job_name}' job ID is ${job_id}"
echo "${job_id}"
fi
}
function play_job() {
local job_name="${1}"
local job_id
job_id=$(get_job_id "${job_name}" "scope=manual");
if [ -z "${job_id}" ]; then return; fi
local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
if [ -z "${api_token}" ]; then
echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
return
fi
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
echoinfo "POST ${url}"
local job_url
job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq ".web_url")
echoinfo "Manual job '${job_name}' started at: ${job_url}"
}
function wait_for_job_to_be_done() {
local job_name="${1}"
local query_string="${2}"
local job_id
job_id=$(get_job_id "${job_name}" "${query_string}")
if [ -z "${job_id}" ]; then return; fi
local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
if [ -z "${api_token}" ]; then
echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
return
fi
echoinfo "Waiting for the '${job_name}' job to finish..."
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}"
echoinfo "GET ${url}"
# In case the job hasn't finished yet. Keep trying until the job times out.
local interval=30
local elapsed_seconds=0
while true; do
local job_status
job_status=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq ".status" | sed -e s/\"//g)
[[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break
printf "."
let "elapsed_seconds+=interval"
sleep ${interval}
done
local elapsed_minutes=$((elapsed_seconds / 60))
echoinfo "Waited '${job_name}' for ${elapsed_minutes} minutes."
if [[ "${job_status}" == "failed" ]]; then
echoerr "The '${job_name}' failed."
elif [[ "${job_status}" == "manual" ]]; then
echoinfo "The '${job_name}' is manual."
else
echoinfo "The '${job_name}' passed."
fi
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment