Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parallelism #51

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
export timing data to circleci
eddiewebb committed Jan 14, 2020
commit 6caaff45d3a223c387cda314286d0df2dca5a8aa
8 changes: 6 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -80,10 +80,14 @@ jobs:
name: Import Tests using BATS
command: |
export BATS_IMPORT_DEV_ORB="eddiewebb/<<pipeline.parameters.orbname>>@dev:${PR_NUMBER}"
export BATS_TEST_LIST=$(bats -l test | circleci tests split | tr '\n' ' ')
export BATS_TEST_LIST=$(bats -l test | circleci tests split --split-by=timings | tr '\n' ' ')
echo "This node will run ${BATS_TEST_LIST}"
bats test
mkdir -p reports
bats test | tee reports/test-report
bash src/parse-taps.bash reports/test-report > reports/bats_junit_report.xml
- pr-comment
- store_test_results:
path: reports

publish:
docker:
27 changes: 27 additions & 0 deletions src/parse-taps.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

head='<?xml version="1.0"?>\n<testsuite>'
foot='</testsuite>'

test='<testcase name="%s" time="%d" />'

parse_it(){
$line=$1
NAME=$(expr "$line" : '.*ok [0-9]* \(.*\) #time.*')
TIME=$(expr "$line" : '.*ok [0-9]*.*\#time=\(.*\)')
printf "${test}" "${NAME}" ${TIME}
}


printf "$head"

while read -r line;do
case $line in
1..*) continue ;;
ok*) parse_it $line;;
\#*) continue;;
*) echo "unkon line" ;;
esac
done < "$1"

printf "\n$foot"
207 changes: 207 additions & 0 deletions src/queue.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@

#
#.This query builds and determine our place in queue
#
main_loop(){
load_variables
max_time=<< parameters.time >>
echo "This build will block until all previous builds complete."
echo "Max Queue Time: ${max_time} minutes."
wait_time=0
loop_time=10
max_time_seconds=$((max_time * 60))


load_current_job_details #gives us our pipeline ID & Order



#get recent pipeline for this project (optionally filtering on branch)
# for each pipeline, is ID lower than ours?
# if lower, get all workflows
# for each workflow, are they running?
# if job-specific running, do they contain my job?
# v2 api sucks.




#
# Queue Loop
#
confidence=0
while true; do
update_comparables
echo "This Workflow Timestamp: $my_commit_time"
echo "Oldest Workflow Timestamp: $oldest_commit_time"
if [[ "$oldest_commit_time" > "$my_commit_time" ]] || [[ "$oldest_commit_time" = "$my_commit_time" ]] ; then
# API returns Y-M-D HH:MM (with 24 hour clock) so alphabetical string compare is accurate to timestamp compare as well
# recent-jobs API does not include pending, so it is posisble we queried in between a workfow transition, and we;re NOT really front of line.
if [ $confidence -lt <<parameters.confidence>> ];then
# To grow confidence, we check again with a delay.
confidence=$((confidence+1))
else
echo "Front of the line, WooHoo!, Build continuing"
break
fi
else
echo "This build (${CIRCLE_BUILD_NUM}) is queued, waiting for build number (${oldest_running_build_num}) to complete."
echo "Total Queue time: ${wait_time} seconds."
fi

if [ $wait_time -ge $max_time_seconds ]; then
echo "Max wait time exceeded, considering response."
if [ "<<parameters.dont-quit>>" == "true" ];then
echo "Orb parameter dont-quit is set to true, letting this job proceed!"
exit 0
else
cancel_current_build
sleep 10 # wait for API to cancel this job, rather than showing as failure
exit 1 # but just in case, fail job
fi
fi

sleep $loop_time
wait_time=$(( loop_time + wait_time ))
done

}



load_variables(){
# just confirm our required variables are present
: ${CIRCLE_BUILD_NUM:?"Required Env Variable not found!"}
: ${CIRCLE_PROJECT_USERNAME:?"Required Env Variable not found!"}
: ${CIRCLE_PROJECT_REPONAME:?"Required Env Variable not found!"}
: ${CIRCLE_REPOSITORY_URL:?"Required Env Variable not found!"}
: ${CIRCLE_JOB:?"Required Env Variable not found!"}
: ${CIRCLE_WORKFLOW_ID:?"Required Env Variable not found!"}
# Only needed for private projects
if [ -z "$CIRCLECI_API_KEY" ]; then
echo "ERROR: CIRCLECI_API_KEY not set. API will be inaccessible." >&2
exit 1
fi
VCS_TYPE="<<parameters.vcs-type>>"
}

load_current_pipeline_info(){
#get_api_payload "https://circleci.com/api/v2/project/${VCS_TYPE}/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/job/${CIRCLE_BUILD_NUM}" /tmp/current_workflow.json
get_api_payload "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}" /tmp/current_workflow.json
CIRCLE_PIPELINE_ID=$(jq '.pipeline_id' /tmp/current_workflow.json) #UUID
CIRCLE_PIPELINE_NUMBER=$(jq '.pipeline_number' /tmp/current_workflow.json) #RelativeOrder
}


get_api_payload(){
url=$1
target=$2
curl -X GET "${url}" -H "Accept: application/json" -H "Circle-Token: ${CIRCLECI_API_KEY}" > ${target}
if [ $? -ne 0 ];then
echo "ERROR: Curl command to ${url} failed. Response below."
cat $target
exit 1
fi
}


fetch_filtered_active_builds(){
if [ "<<parameters.consider-branch>>" != "true" ];then
echo "Orb parameter 'consider-branch' is false, will block previous builds on any branch."
jobs_api_url_template="https://circleci.com/api/v1.1/project/${VCS_TYPE}/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}?circle-token=${CIRCLECI_API_KEY}&filter=running"
else
echo "Only blocking execution if running previous jobs on branch: ${CIRCLE_BRANCH}"
: ${CIRCLE_BRANCH:?"Required Env Variable not found!"}
jobs_api_url_template="https://circleci.com/api/v1.1/project/${VCS_TYPE}/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/tree/${CIRCLE_BRANCH}?circle-token=${CIRCLECI_API_KEY}&filter=running"
fi

if [ ! -z $TESTING_MOCK_RESPONSE ] && [ -f $TESTING_MOCK_RESPONSE ];then
echo "Using test mock response"
cat $TESTING_MOCK_RESPONSE > /tmp/jobstatus.json
else
echo "Attempting to access CircleCI api. If the build process fails after this step, ensure your CIRCLECI_API_KEY is set."
curl -f -s $jobs_api_url_template > /tmp/jobstatus.json
echo "API access successful"
fi
}

fetch_active_workflows(){
cp /tmp/jobstatus.json /tmp/augmented_jobstatus.json
for workflow in `jq -r ".[] | .workflows.workflow_id" /tmp/augmented_jobstatus.json | uniq`
do
echo "Checking time of workflow: ${workflow}"
workflow_file=/tmp/workflow-${workflow}.json
if [ ! -z $TESTING_MOCK_WORKFLOW_RESPONSES ] && [ -f $TESTING_MOCK_WORKFLOW_RESPONSES/${workflow}.json ]; then
echo "Using test mock workflow response"
cat $TESTING_MOCK_WORKFLOW_RESPONSES/${workflow}.json > ${workflow_file}
else
curl -f -s "https://circleci.com/api/v2/workflow/${workflow}?circle-token=${CIRCLECI_API_KEY}" > ${workflow_file}
fi
created_at=`jq -r '.created_at' ${workflow_file}`
echo "Workflow was created at: ${created_at}"
cat /tmp/augmented_jobstatus.json | jq --arg created_at "${created_at}" --arg workflow "${workflow}" '(.[] | select(.workflows.workflow_id == $workflow) | .workflows) |= . + {created_at:$created_at}' > /tmp/augmented_jobstatus-${workflow}.json
#DEBUG echo "new augmented_jobstatus:"
#DEBUG cat /tmp/augmented_jobstatus-${workflow}.json
mv /tmp/augmented_jobstatus-${workflow}.json /tmp/augmented_jobstatus.json
done
}

update_comparables(){
fetch_filtered_active_builds

fetch_active_workflows

load_current_workflow_values

# falsey parameters are empty strings, so always compare against 'true'
if [ "<<parameters.consider-job>>" != "true" ] || [ "<<parameters.block-workflow>>" = "true" ] ;then
echo "Orb parameter block-workflow is true."
echo "This job will block until no previous workflows have *any* jobs running."
oldest_running_build_num=`jq 'sort_by(.workflows.created_at)| .[0].build_num' /tmp/augmented_jobstatus.json`
oldest_commit_time=`jq 'sort_by(.workflows.created_at)| .[0].workflows.created_at' /tmp/augmented_jobstatus.json`
else
echo "Orb parameter block-workflow is false."
echo "Only blocking execution if running previous jobs matching this job: ${CIRCLE_JOB}"
oldest_running_build_num=`jq ". | map(select(.build_parameters.CIRCLE_JOB==\"${CIRCLE_JOB}\")) | sort_by(.workflows.created_at)| .[0].build_num" /tmp/augmented_jobstatus.json`
oldest_commit_time=`jq ". | map(select(.build_parameters.CIRCLE_JOB==\"${CIRCLE_JOB}\")) | sort_by(.workflows.created_at)| .[0].workflows.created_at" /tmp/augmented_jobstatus.json`
fi
echo "Oldest job: $oldest_running_build_num"
if [ -z $oldest_commit_time ];then
echo "API Call for existing jobs failed, failing this build. Please check API token"
echo "All running jobs:"
cat /tmp/jobstatus.json || exit 0
echo "All running jobs with created_at:"
cat /tmp/augmented_jobstatus.json || exit 0
echo "All worfklow details."
cat /tmp/workflow-*.json
exit 1
fi
}

load_current_workflow_values(){
my_commit_time=`jq '.[] | select( .build_num == '"${CIRCLE_BUILD_NUM}"').workflows.created_at' /tmp/augmented_jobstatus.json`
}

cancel_current_build(){
echo "Cancelleing build ${CIRCLE_BUILD_NUM}"
cancel_api_url_template="https://circleci.com/api/v1.1/project/${VCS_TYPE}/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BUILD_NUM}/cancel?circle-token=${CIRCLECI_API_KEY}"
curl -s -X POST $cancel_api_url_template > /dev/null
}



#
# We can skip a few use cases without calling API
#
if [ ! -z "$CIRCLE_PR_REPONAME" ]; then
echo "Queueing on forks is not supported. Skipping queue..."
# It's important that we not fail here because it could cause issues on the main repo's branch
exit 0
fi
if [ "<<parameters.only-on-branch>>" = "*" ] || [ "<<parameters.only-on-branch>>" = "${CIRCLE_BRANCH}" ]; then
echo "${CIRCLE_BRANCH} queueable"
else
echo "Queueing only happens on <<parameters.only-on-branch>> branch, skipping queue"
exit 0
fi

1 change: 0 additions & 1 deletion test/test_expansion.bats
Original file line number Diff line number Diff line change
@@ -32,7 +32,6 @@ function setup {
# when
assert_jq_match '.jobs | length' 1 #only 1 job
assert_jq_match '.jobs["Single File"].steps | length' 1 #only 1 steps

}