Commit cfea26f5 authored by Raphael Defosseux's avatar Raphael Defosseux

CI: initial pipeline definition

Signed-off-by: default avatarRaphael Defosseux <raphael.defosseux@eurecom.fr>
parent 3922a836
#!/bin/groovy
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
//-------------------------------------------------------------------------------
// Location of the CN executor node
def cn_ci_host = params.Host_CN_CI_Server
// for lock
def cn_ci_resource = params.DockerContainers
// Location of the 2nd CN executor
def new_host_flag = false
def new_host = ""
def new_host_user = ""
// Tags/Branches to use
def nrf_tag = "develop"
def nrf_branch = "develop"
//-------------------------------------------------------------------------------
// Pipeline start
pipeline {
agent {
label cn_ci_host
}
options {
disableConcurrentBuilds()
timestamps()
ansiColor('xterm')
lock(cn_ci_resource)
gitLabConnection('OAI GitLab')
gitlabBuilds(builds: [
"Build NRF Image",
"Static Code Analysis",
"Code Formatting Checker"
])
}
stages {
stage ('Verify Parameters') {
steps {
script {
echo '\u2705 \u001B[32mVerify Parameters\u001B[0m'
JOB_TIMESTAMP = sh returnStdout: true, script: 'date --utc --rfc-3339=seconds | sed -e "s#+00:00##"'
JOB_TIMESTAMP = JOB_TIMESTAMP.trim()
if (params.Host_CN_CI_2nd_Server_Flag != null) {
new_host_flag = params.Host_CN_CI_2nd_Server_Flag
if (new_host_flag) {
new_host = params.Host_CN_CI_2nd_Server
new_host_user = params.Host_CN_CI_2nd_Server_Login
echo "1st Node is ${NODE_NAME}"
echo "2nd Node is ${new_host}"
} else {
echo "Node is ${NODE_NAME}"
}
} else {
echo "Node is ${NODE_NAME}"
}
echo "Git URL is ${GIT_URL}"
}
}
}
stage ('Prepare Source Code') {
steps {
script {
sh "git clean -x -d -f > /dev/null 2>&1"
if ("MERGE".equals(env.gitlabActionType)) {
gitCommitAuthorEmailAddr = env.gitlabUserEmail
echo "GitLab Usermail is ${gitCommitAuthorEmailAddr}"
sh "./ci-scripts/doGitLabMerge.sh --src-branch ${env.gitlabSourceBranch} --src-commit ${env.gitlabMergeRequestLastCommit} --target-branch ${env.gitlabTargetBranch} --target-commit ${GIT_COMMIT}"
nrf_tag = "ci-tmp"
nrf_branch = env.gitlabSourceBranch
} else {
echo "Git Branch is ${GIT_BRANCH}"
echo "Git Commit is ${GIT_COMMIT}"
gitCommitAuthorEmailAddr = sh returnStdout: true, script: 'git log -n1 --pretty=format:%ae ${GIT_COMMIT}'
gitCommitAuthorEmailAddr = gitCommitAuthorEmailAddr.trim()
echo "GitLab Usermail is ${gitCommitAuthorEmailAddr}"
sh "git log -n1 --pretty=format:\"%s\" > .git/CI_COMMIT_MSG"
}
sh "tar -cjhf /tmp/openair-nrf.tar.bz2 ."
sh "mv /tmp/openair-nrf.tar.bz2 ."
copyTo2ndServer('openair-nrf.tar.bz2', new_host_flag, new_host_user, new_host)
sh "mkdir -p archives"
if (new_host_flag) {
sh "mkdir -p archives/oai-nrf-cfg"
}
}
}
post {
failure {
script {
def message = "OAI " + JOB_NAME + " build (" + BUILD_ID + "): Merge Conflicts -- Cannot perform CI"
addGitLabMRComment comment: message
currentBuild.result = 'FAILURE'
}
}
}
}
stage('Build Core Network Function') {
parallel {
stage ('Build NRF Image') {
steps {
script {
gitlabCommitStatus(name: "Build NRF Image") {
myShCmd('docker image prune --force', new_host_flag, new_host_user, new_host)
if ("PUSH".equals(env.gitlabActionType)) {
// Currently this pipeline only runs for pushes to `develop` branch
// First clean image registry
try {
myShCmd('docker image rm oai-nrf:develop', new_host_flag, new_host_user, new_host)
} catch (Exception e) {
echo "Maybe a previous build went wrong"
}
}
myShCmd('docker build --target oai-nrf --tag oai-nrf:' + nrf_tag + ' --file docker/Dockerfile.ubuntu.18.04 --build-arg NEEDED_GIT_PROXY="http://proxy.eurecom.fr:8080" . > archives/nrf_docker_image_build.log 2>&1', new_host_flag, new_host_user, new_host)
myShCmd('docker image ls >> archives/nrf_docker_image_build.log', new_host_flag, new_host_user, new_host)
}
}
}
post {
always {
script {
copyFrom2ndServer('archives/nrf_docker_image_build.log', 'archives', new_host_flag, new_host_user, new_host)
}
}
success {
sh "echo 'OAI-NRF DOCKER IMAGE BUILD: OK' >> archives/nrf_docker_image_build.log"
}
unsuccessful {
sh "echo 'OAI-NRF DOCKER IMAGE BUILD: KO' >> archives/nrf_docker_image_build.log"
}
}
}
// Running CPPCHECK in parallel to gain time
stage ('Static Code Analysis') {
steps {
script {
gitlabCommitStatus(name: "Static Code Analysis") {
// Running on xenial to have 1.72 version of cppcheck
myShCmd('docker run --name ci-cn-cppcheck -d ubuntu:xenial /bin/bash -c "sleep infinity"', new_host_flag, new_host_user, new_host)
myShCmd('docker exec ci-cn-cppcheck /bin/bash -c "apt-get update && apt-get upgrade --yes" > archives/cppcheck_install.log', new_host_flag, new_host_user, new_host)
myShCmd('docker exec ci-cn-cppcheck /bin/bash -c "apt-get install --yes cppcheck bzip2" >> archives/cppcheck_install.log', new_host_flag, new_host_user, new_host)
myShCmd('docker cp ./openair-nrf.tar.bz2 ci-cn-cppcheck:/home', new_host_flag, new_host_user, new_host)
myShCmd('docker exec ci-cn-cppcheck /bin/bash -c "cd /home && tar -xjf openair-nrf.tar.bz2"', new_host_flag, new_host_user, new_host)
myShCmd('docker exec ci-cn-cppcheck /bin/bash -c "rm -f /home/openair-nrf.tar.bz2"', new_host_flag, new_host_user, new_host)
myShCmd('docker exec ci-cn-cppcheck /bin/bash -c "cd /home && cppcheck --enable=warning --force --xml --xml-version=2 --suppressions-list=ci-scripts/cppcheck_suppressions.list src 2> cppcheck.xml 1> cppcheck_build.log"', new_host_flag, new_host_user, new_host)
}
}
}
post {
always {
script {
myShCmd('docker cp ci-cn-cppcheck:/home/cppcheck.xml archives', new_host_flag, new_host_user, new_host)
myShCmd('docker cp ci-cn-cppcheck:/home/cppcheck_build.log archives', new_host_flag, new_host_user, new_host)
copyFrom2ndServer('archives/cppcheck*.*', 'archives', new_host_flag, new_host_user, new_host)
// no need to keep the cppcheck container
myShCmd('docker rm -f ci-cn-cppcheck', new_host_flag, new_host_user, new_host)
}
}
success {
sh "echo 'CPPCHECK: OK' >> archives/cppcheck_install.log"
}
unsuccessful {
sh "echo 'CPPCHECK: KO' >> archives/cppcheck_install.log"
}
}
}
// Running CLANG-FORMATTING check in parallel to gain time
stage ('Code Formatting Checker') {
steps {
script {
gitlabCommitStatus(name: "Code Formatting Checker") {
myShCmd('docker run --name ci-cn-clang-formatter -d ubuntu:bionic /bin/bash -c "sleep infinity"', new_host_flag, new_host_user, new_host)
myShCmd('docker exec ci-cn-clang-formatter /bin/bash -c "apt-get update && apt-get upgrade --yes" > archives/clang_format_install.log', new_host_flag, new_host_user, new_host)
myShCmd('docker exec ci-cn-clang-formatter /bin/bash -c "apt-get install --yes git tree bzip2" >> archives/clang_format_install.log', new_host_flag, new_host_user, new_host)
myShCmd('docker cp ./openair-nrf.tar.bz2 ci-cn-clang-formatter:/home', new_host_flag, new_host_user, new_host)
myShCmd('docker exec ci-cn-clang-formatter /bin/bash -c "cd /home && tar -xjf openair-nrf.tar.bz2"', new_host_flag, new_host_user, new_host)
myShCmd('docker exec ci-cn-clang-formatter /bin/bash -c "rm -f /home/openair-nrf.tar.bz2"', new_host_flag, new_host_user, new_host)
// We install a dedicated version (installed on our CI server).
myShCmd('docker cp /opt/clang-format/9.0.0/bin/clang-format ci-cn-clang-formatter:/usr/local/bin', new_host_flag, new_host_user, new_host)
if ("MERGE".equals(env.gitlabActionType)) {
myShCmd('docker exec ci-cn-clang-formatter /bin/bash -c "cd /home && ./ci-scripts/checkCodingFormattingRules.sh --src-branch ' + env.gitlabSourceBranch +' --target-branch ' + env.gitlabTargetBranch + '"', new_host_flag, new_host_user, new_host)
} else {
myShCmd('docker exec ci-cn-clang-formatter /bin/bash -c "cd /home && ./ci-scripts/checkCodingFormattingRules.sh"', new_host_flag, new_host_user, new_host)
}
}
}
}
post {
always {
script {
myShCmd('docker cp ci-cn-clang-formatter:/home/src/oai_rules_result.txt src', new_host_flag, new_host_user, new_host)
// May not have been generated
try {
myShCmd('docker cp ci-cn-clang-formatter:/home/src/oai_rules_result_list.txt src', new_host_flag, new_host_user, new_host)
} catch (Exception e) {
echo "Failed to copy src/oai_rules_result_list.txt! It may not have been generated. That's OK!"
}
copyFrom2ndServer('archives/clang_format*.*', 'archives', new_host_flag, new_host_user, new_host)
copyFrom2ndServer('src/oai_rules*.*', 'src', new_host_flag, new_host_user, new_host)
// no need to keep the clang-formatter container
myShCmd('docker rm -f ci-cn-clang-formatter', new_host_flag, new_host_user, new_host)
}
}
}
}
}
}
}
post {
success {
script {
if ("MERGE".equals(env.gitlabActionType)) {
def message = "OAI " + JOB_NAME + " build (" + BUILD_ID + "): passed (" + BUILD_URL + ")"
echo "This is a MERGE event"
addGitLabMRComment comment: message
}
}
}
unsuccessful {
script {
if ("MERGE".equals(env.gitlabActionType)) {
def message = "OAI " + JOB_NAME + " build (" + BUILD_ID + "): failed (" + BUILD_URL + ")"
echo "This is a MERGE event"
addGitLabMRComment comment: message
}
}
}
cleanup {
script {
// Removing temporary / intermediate images
try {
if ("MERGE".equals(env.gitlabActionType)) {
myShCmd('docker image rm --force oai-nrf:ci-tmp', new_host_flag, new_host_user, new_host)
}
} catch (Exception e) {
echo "We failed to delete the OAI-NRF temp image"
}
try {
myShCmd('docker image prune --force', new_host_flag, new_host_user, new_host)
} catch (Exception e) {
echo "We failed to prune all unneeded intermediate images"
}
// Zipping all archived log files
sh "zip -r -qq docker_logs.zip archives"
if (fileExists('docker_logs.zip')) {
archiveArtifacts artifacts: 'docker_logs.zip'
}
// Generating the HTML report
if ("MERGE".equals(env.gitlabActionType)) {
sh "python3 ci-scripts/generateHtmlReport.py --job_name=${JOB_NAME} --job_id=${BUILD_ID} --job_url=${BUILD_URL} --git_url=${GIT_URL} --git_src_branch=${env.gitlabSourceBranch} --git_src_commit=${env.gitlabMergeRequestLastCommit} --git_pull_request=True --git_target_branch=${env.gitlabTargetBranch} --git_target_commit=${GIT_COMMIT}"
} else {
sh "python3 ci-scripts/generateHtmlReport.py --job_name=${JOB_NAME} --job_id=${BUILD_ID} --job_url=${BUILD_URL} --git_url=${GIT_URL} --git_src_branch=${GIT_BRANCH} --git_src_commit=${GIT_COMMIT}"
}
sh "sed -i -e 's#TEMPLATE_TIME#${JOB_TIMESTAMP}#' test_results_oai_nrf.html"
if (fileExists('test_results_oai_nrf.html')) {
archiveArtifacts artifacts: 'test_results_oai_nrf.html'
}
if (fileExists('deploy_results_oai_cn5g.html')) {
sh "sed -i -e 's#TEMPLATE_TIME#${JOB_TIMESTAMP}#' *_results_oai_cn5g.html"
archiveArtifacts artifacts: '*_results_oai_cn5g.html'
}
// Sending email to commiter
if (params.sendToCommitterEmail != null) {
if (params.sendToCommitterEmail) {
emailext attachmentsPattern: '*results*.html',
body: '''Hi,
Here are attached HTML report files for $PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!
Regards,
OAI CI Team''',
replyTo: 'no-reply@openairinterface.org',
subject: '$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!',
to: gitCommitAuthorEmailAddr
}
}
}
}
}
}
def copyTo2ndServer(filename, flag, user, host) {
if (flag) {
if ("openair-nrf.tar.bz2".equals(filename)) {
sh "ssh ${user}@${host} 'rm -rf /tmp/CI-CN-NRF'"
sh "ssh ${user}@${host} 'mkdir -p /tmp/CI-CN-NRF'"
}
sh "scp ${filename} ${user}@${host}:/tmp/CI-CN-NRF"
if ("openair-nrf.tar.bz2".equals(filename)) {
sh "ssh ${user}@${host} 'cd /tmp/CI-CN-NRF && tar -xjf ${filename}'"
sh "ssh ${user}@${host} 'mkdir -p /tmp/CI-CN-NRF/archives'"
sh "ssh ${user}@${host} 'mkdir -p /tmp/CI-CN-NRF/archives/oai-nrf-cfg'"
}
}
}
def copyFrom2ndServer(filename, target, flag, user, host) {
if (flag) {
sh "scp ${user}@${host}:/tmp/CI-CN-NRF/${filename} ${target}"
}
}
def myShCmd(cmd, flag, user, host) {
if (flag) {
sh "ssh -t -t ${user}@${host} 'cd /tmp/CI-CN-NRF && ${cmd}'"
} else {
sh "${cmd}"
}
}
def myShRetCmd(cmd, flag, user, host) {
if (flag) {
ret = sh returnStdout: true, script: "ssh -t -t ${user}@${host} 'cd /tmp/CI-CN-NRF && ${cmd}'"
} else {
ret = sh returnStdout: true, script: "${cmd}"
}
ret = ret.trim()
return ret
}
#!/bin/bash
#/*
# * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
# * contributor license agreements. See the NOTICE file distributed with
# * this work for additional information regarding copyright ownership.
# * The OpenAirInterface Software Alliance licenses this file to You under
# * the OAI Public License, Version 1.1 (the "License"); you may not use this file
# * except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * http://www.openairinterface.org/?page_id=698
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an "AS IS" BASIS,
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
# *-------------------------------------------------------------------------------
# * For more information about the OpenAirInterface (OAI) Software Alliance:
# * contact@openairinterface.org
# */
function usage {
echo "OAI Coding / Formatting Guideline Check script"
echo " Original Author: Raphael Defosseux"
echo ""
echo " Requirement: clang-format / git shall be installed"
echo ""
echo " By default (no options) the complete repository will be checked"
echo " In case of merge/pull request, provided source and target branch,"
echo " the script will check only the modified files"
echo ""
echo "Usage:"
echo "------"
echo " checkCodingFormattingRules.sh [OPTIONS]"
echo ""
echo "Options:"
echo "--------"
echo " --src-branch #### OR -sb ####"
echo " Specify the source branch of the merge request."
echo ""
echo " --target-branch #### OR -tb ####"
echo " Specify the target branch of the merge request (usually develop)."
echo ""
echo " --help OR -h"
echo " Print this help message."
echo ""
}
if [ $# -ne 4 ] && [ $# -ne 1 ] && [ $# -ne 0 ]
then
echo "Syntax Error: not the correct number of arguments"
echo ""
usage
exit 1
fi
cd src
if [ $# -eq 0 ]
then
echo " ---- Checking the whole repository ----"
echo ""
if [ -f oai_rules_result.txt ]
then
rm -f oai_rules_result.txt
fi
if [ -f oai_rules_result_list.txt ]
then
rm -f oai_rules_result_list.txt
fi
EXTENSION_LIST=("h" "hpp" "c" "cpp")
NB_TO_FORMAT=0
NB_TOTAL=0
for EXTENSION in ${EXTENSION_LIST[@]}
do
echo "Checking for all files with .${EXTENSION} extension"
FILE_LIST=`tree -n --noreport -i -f -P *.${EXTENSION} | sed -e 's#^\./##' | grep "\.${EXTENSION}"`
for FILE_TO_CHECK in ${FILE_LIST[@]}
do
TO_FORMAT=`clang-format -output-replacements-xml ${FILE_TO_CHECK} 2>&1 | grep -v replacements | grep -c replacement`
NB_TOTAL=$((NB_TOTAL + 1))
if [ $TO_FORMAT -ne 0 ]
then
NB_TO_FORMAT=$((NB_TO_FORMAT + 1))
# In case of full repo, being silent
#echo "src/$FILE_TO_CHECK"
echo "src/$FILE_TO_CHECK" >> ./oai_rules_result_list.txt
fi
done
done
echo "Nb Files that do NOT follow OAI rules: $NB_TO_FORMAT over $NB_TOTAL checked!"
echo "NB_FILES_FAILING_CHECK=$NB_TO_FORMAT" > ./oai_rules_result.txt
echo "NB_FILES_CHECKED=$NB_TOTAL" >> ./oai_rules_result.txt
exit 0
fi
checker=0
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-h|--help)
shift
usage
exit 0
;;
-sb|--src-branch)
SOURCE_BRANCH="$2"
let "checker|=0x1"
shift
shift
;;
-tb|--target-branch)
TARGET_BRANCH="$2"
let "checker|=0x2"
shift
shift
;;
*)
echo "Syntax Error: unknown option: $key"
echo ""
usage
exit 1
esac
done
if [ $checker -ne 3 ]
then
echo "Source Branch is : $SOURCE_BRANCH"
echo "Target Branch is : $TARGET_BRANCH"
echo ""
echo "Syntax Error: missing option"
echo ""
usage
exit 1
fi
# Merge request scenario
MERGE_COMMMIT=`git log -n1 --pretty=format:%H`
if [ -f .git/refs/remotes/origin/$TARGET_BRANCH ]
then
TARGET_INIT_COMMIT=`cat .git/refs/remotes/origin/$TARGET_BRANCH`
else
TARGET_INIT_COMMIT=`git log -n1 --pretty=format:%H origin/$TARGET_BRANCH`
fi
echo " ---- Checking the modified files by the merge request ----"
echo ""
echo "Source Branch is : $SOURCE_BRANCH"
echo "Target Branch is : $TARGET_BRANCH"
echo "Merged Commit is : $MERGE_COMMMIT"
echo "Target Init is : $TARGET_INIT_COMMIT"
echo ""
echo " ----------------------------------------------------------"
echo ""
# Retrieve the list of modified files since the latest develop commit
MODIFIED_FILES=`git log $TARGET_INIT_COMMIT..$MERGE_COMMMIT --oneline --name-status | egrep "^M|^A" | sed -e "s@^M\t*@@" -e "s@^A\t*@@" | sort | uniq`
NB_TO_FORMAT=0
NB_TOTAL=0
if [ -f oai_rules_result.txt ]
then
rm -f oai_rules_result.txt
fi
if [ -f oai_rules_result_list.txt ]
then
rm -f oai_rules_result_list.txt
fi
for FULLFILE in $MODIFIED_FILES
do
filename=$(basename -- "$FULLFILE")
EXT="${filename##*.}"
if [ $EXT = "c" ] || [ $EXT = "h" ] || [ $EXT = "cpp" ] || [ $EXT = "hpp" ]
then
SRC_FILE=`echo $FULLFILE | sed -e "s#src/##"`
TO_FORMAT=`clang-format -output-replacements-xml ${SRC_FILE} 2>&1 | grep -v replacements | grep -c replacement`
NB_TOTAL=$((NB_TOTAL + 1))
if [ $TO_FORMAT -ne 0 ]
then
NB_TO_FORMAT=$((NB_TO_FORMAT + 1))
echo $FULLFILE
echo $FULLFILE >> ./oai_rules_result_list.txt
fi
fi
done
echo ""
echo " ----------------------------------------------------------"
echo "Nb Files that do NOT follow OAI rules: $NB_TO_FORMAT over $NB_TOTAL checked!"
echo "NB_FILES_FAILING_CHECK=$NB_TO_FORMAT" > ./oai_rules_result.txt
echo "NB_FILES_CHECKED=$NB_TOTAL" >> ./oai_rules_result.txt
exit 0
// *INDENT-OFF* cppcheck doesn't like "astyling" this file!!!!
// /*
// * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
// * contributor license agreements. See the NOTICE file distributed with
// * this work for additional information regarding copyright ownership.
// * The OpenAirInterface Software Alliance licenses this file to You under
// * the OAI Public License, Version 1.1 (the "License"); you may not use this file
// * except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.openairinterface.org/?page_id=698
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// *-------------------------------------------------------------------------------
// * For more information about the OpenAirInterface (OAI) Software Alliance:
// * contact@openairinterface.org
// */
//*****************************************************************************
//-----------------------------------------------------------------------------
// *INDENT-ON*
#!/bin/bash
#/*
# * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
# * contributor license agreements. See the NOTICE file distributed with
# * this work for additional information regarding copyright ownership.
# * The OpenAirInterface Software Alliance licenses this file to You under
# * the OAI Public License, Version 1.1 (the "License"); you may not use this file
# * except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * http://www.openairinterface.org/?page_id=698
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an "AS IS" BASIS,
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
# *-------------------------------------------------------------------------------
# * For more information about the OpenAirInterface (OAI) Software Alliance:
# * contact@openairinterface.org
# */
function usage {
echo "OAI GitLab merge request applying script"
echo " Original Author: Raphael Defosseux"
echo ""
echo "Usage:"
echo "------"
echo ""
echo " doGitLabMerge.sh [OPTIONS] [MANDATORY_OPTIONS]"
echo ""
echo "Mandatory Options:"
echo "------------------"
echo ""
echo " --src-branch #### OR -sb ####"
echo " Specify the source branch of the merge request."
echo ""
echo " --src-commit #### OR -sc ####"
echo " Specify the source commit ID (SHA-1) of the merge request."
echo ""
echo " --target-branch #### OR -tb ####"
echo " Specify the target branch of the merge request (usually develop)."
echo ""
echo " --target-commit #### OR -tc ####"
echo " Specify the target commit ID (SHA-1) of the merge request."
echo ""
echo "Options:"
echo "--------"
echo " --help OR -h"
echo " Print this help message."
echo ""
}
if [ $# -ne 8 ] && [ $# -ne 1 ]
then
echo "Syntax Error: not the correct number of arguments"
echo ""
usage
exit 1
fi
checker=0
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-h|--help)
shift
usage
exit 0
;;
-sb|--src-branch)
SOURCE_BRANCH="$2"
let "checker|=0x1"
shift
shift
;;
-sc|--src-commit)
SOURCE_COMMIT_ID="$2"
let "checker|=0x2"
shift
shift
;;
-tb|--target-branch)
TARGET_BRANCH="$2"
let "checker|=0x4"
shift
shift
;;
-tc|--target-commit)
TARGET_COMMIT_ID="$2"
let "checker|=0x8"
shift
shift
;;
*)
echo "Syntax Error: unknown option: $key"
echo ""
usage
exit 1
esac
done
if [[ $TARGET_COMMIT_ID == "latest" ]]
then
TARGET_COMMIT_ID=`git log -n1 --pretty=format:%H origin/$TARGET_BRANCH`
fi
echo "Source Branch is : $SOURCE_BRANCH"
echo "Source Commit ID is : $SOURCE_COMMIT_ID"
echo "Target Branch is : $TARGET_BRANCH"
echo "Target Commit ID is : $TARGET_COMMIT_ID"
if [ $checker -ne 15 ]
then
echo ""
echo "Syntax Error: missing option"
echo ""
usage
exit 1
fi
git config user.email "jenkins@openairinterface.org"
git config user.name "OAI Jenkins"
git checkout -f $SOURCE_COMMIT_ID > checkout.txt 2>&1
STATUS=`egrep -c "fatal: reference is not a tree" checkout.txt`
rm -f checkout.txt
if [ $STATUS -ne 0 ]
then
echo "fatal: reference is not a tree --> $SOURCE_COMMIT_ID"
STATUS=-1
exit $STATUS
fi
git log -n1 --pretty=format:\"%s\" > .git/CI_COMMIT_MSG
git merge --ff $TARGET_COMMIT_ID -m "Temporary merge for CI"
STATUS=`git status | egrep -c "You have unmerged paths.|fix conflicts"`
if [ $STATUS -ne 0 ]
then
echo "There are merge conflicts.. Cannot perform further build tasks"
STATUS=-1
fi
exit $STATUS
#/*
# * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
# * contributor license agreements. See the NOTICE file distributed with
# * this work for additional information regarding copyright ownership.
# * The OpenAirInterface Software Alliance licenses this file to You under
# * the OAI Public License, Version 1.1 (the "License"); you may not use this file
# * except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * http://www.openairinterface.org/?page_id=698
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an "AS IS" BASIS,
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
# *-------------------------------------------------------------------------------
# * For more information about the OpenAirInterface (OAI) Software Alliance:
# * contact@openairinterface.org
# */
#---------------------------------------------------------------------
import os
import re
import sys
import subprocess
class HtmlReport():
def __init__(self):
self.job_name = ''
self.job_id = ''
self.job_url = ''
self.job_start_time = 'TEMPLATE_TIME'
self.git_url = ''
self.git_src_branch = ''
self.git_src_commit = ''
self.git_src_commit_msg = None
self.git_pull_request = False
self.git_target_branch = ''
self.git_target_commit = ''
self.nb_warnings = 0
self.warning_rows = ''
def generate(self):
cwd = os.getcwd()
self.file = open(cwd + '/test_results_oai_nrf.html', 'w')
self.generateHeader()
self.coding_formatting_log()
self.analyze_sca_log()
self.buildSummaryHeader()
self.initialGitSetup()
self.installLibsPackagesRow()
self.buildCompileRows()
self.copyToTargetImage()
self.copyConfToolsToTargetImage()
self.imageSizeRow()
self.buildSummaryFooter()
self.generateFooter()
self.file.close()
def generateHeader(self):
# HTML Header
self.file.write('<!DOCTYPE html>\n')
self.file.write('<html class="no-js" lang="en-US">\n')
self.file.write('<head>\n')
self.file.write(' <meta name="viewport" content="width=device-width, initial-scale=1">\n')
self.file.write(' <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">\n')
self.file.write(' <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>\n')
self.file.write(' <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>\n')
self.file.write(' <title>OAI Core Network Test Results for ' + self.job_name + ' job build #' + self.job_id + '</title>\n')
self.file.write('</head>\n')
self.file.write('<body><div class="container">\n')
self.file.write(' <table width = "100%" style="border-collapse: collapse; border: none;">\n')
self.file.write(' <tr style="border-collapse: collapse; border: none;">\n')
self.file.write(' <td style="border-collapse: collapse; border: none;">\n')
self.file.write(' <a href="http://www.openairinterface.org/">\n')
self.file.write(' <img src="http://www.openairinterface.org/wp-content/uploads/2016/03/cropped-oai_final_logo2.png" alt="" border="none" height=50 width=150>\n')
self.file.write(' </img>\n')
self.file.write(' </a>\n')
self.file.write(' </td>\n')
self.file.write(' <td style="border-collapse: collapse; border: none; vertical-align: center;">\n')
self.file.write(' <b><font size = "6">Job Summary -- Job: ' + self.job_name + ' -- Build-ID: <a href="' + self.job_url + '">' + self.job_id + '</a></font></b>\n')
self.file.write(' </td>\n')
self.file.write(' </tr>\n')
self.file.write(' </table>\n')
self.file.write(' <br>\n')
# Build Info Summary
buildSummary = ''
buildSummary += ' <table class="table-bordered" width = "80%" align = "center" border = "1">\n'
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-time"></span> Build Start Time</td>\n'
#date_formatted = re.sub('\..*', '', self.created)
buildSummary += ' <td>' + self.job_start_time + '</td>\n'
buildSummary += ' </tr>\n'
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-wrench"></span> Build Trigger</td>\n'
if self.git_pull_request:
buildSummary += ' <td>Pull Request</td>\n'
else:
buildSummary += ' <td>Push Event</td>\n'
buildSummary += ' </tr>\n'
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-cloud-upload"></span> GIT Repository</td>\n'
buildSummary += ' <td><a href="' + self.git_url + '">' + self.git_url + '</a></td>\n'
buildSummary += ' </tr>\n'
if self.git_pull_request:
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-log-out"></span> Source Branch</td>\n'
buildSummary += ' <td>' + self.git_src_branch + '</td>\n'
buildSummary += ' </tr>\n'
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-tag"></span> Source Commit ID</td>\n'
buildSummary += ' <td>' + self.git_src_commit + '</td>\n'
buildSummary += ' </tr>\n'
if (self.git_src_commit_msg is not None):
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-comment"></span> Source Commit Message</td>\n'
buildSummary += ' <td>' + self.git_src_commit_msg + '</td>\n'
buildSummary += ' </tr>\n'
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-log-in"></span> Target Branch</td>\n'
buildSummary += ' <td>' + self.git_target_branch + '</td>\n'
buildSummary += ' </tr>\n'
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-tag"></span> Target Commit ID</td>\n'
buildSummary += ' <td>' + self.git_target_commit + '</td>\n'
buildSummary += ' </tr>\n'
else:
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-tree-deciduous"></span> Branch</td>\n'
buildSummary += ' <td>' + self.git_src_branch + '</td>\n'
buildSummary += ' </tr>\n'
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-tag"></span> Commit ID</td>\n'
buildSummary += ' <td>' + self.git_src_commit + '</td>\n'
buildSummary += ' </tr>\n'
if (self.git_src_commit_msg is not None):
buildSummary += ' <tr>\n'
buildSummary += ' <td bgcolor="lightcyan" > <span class="glyphicon glyphicon-comment"></span> Commit Message</td>\n'
buildSummary += ' <td>' + self.git_src_commit_msg + '</td>\n'
buildSummary += ' </tr>\n'
buildSummary += ' </table>\n'
buildSummary += ' <br>\n'
self.file.write(buildSummary)
cwd = os.getcwd()
if os.path.isfile(cwd + '/ds_tester_results_oai_cn5g.html'):
newEpcReport = open(cwd + '/ds_tester_results_oai_cn5g_new.html', 'w')
buildSummaryDone = True
with open(cwd + '/ds_tester_results_oai_cn5g.html', 'r') as originalEpcReport:
for line in originalEpcReport:
result = re.search('DS Tester Summary', line)
if (result is not None) and buildSummaryDone:
newEpcReport.write(buildSummary)
buildSummaryDone = False
newEpcReport.write(line)
originalEpcReport.close()
newEpcReport.close()
os.rename(cwd + '/ds_tester_results_oai_cn5g_new.html', cwd + '/ds_tester_results_oai_cn5g.html')
if os.path.isfile(cwd + '/deploy_results_oai_cn5g.html'):
newEpcReport = open(cwd + '/deploy_results_oai_cn5g_new.html', 'w')
buildSummaryDone = True
with open(cwd + '/deploy_results_oai_cn5g.html', 'r') as originalEpcReport:
for line in originalEpcReport:
result = re.search('Deployment Summary', line)
if (result is not None) and buildSummaryDone:
newEpcReport.write(buildSummary)
buildSummaryDone = False
newEpcReport.write(line)
originalEpcReport.close()
newEpcReport.close()
os.rename(cwd + '/deploy_results_oai_cn5g_new.html', cwd + '/deploy_results_oai_cn5g.html')
def generateFooter(self):
self.file.write(' <div class="well well-lg">End of Build Report -- Copyright <span class="glyphicon glyphicon-copyright-mark"></span> 2020 <a href="http://www.openairinterface.org/">OpenAirInterface</a>. All Rights Reserved.</div>\n')
self.file.write('</div></body>\n')
self.file.write('</html>\n')
def coding_formatting_log(self):
cwd = os.getcwd()
self.file.write(' <h2>OAI Coding / Formatting Guidelines Check</h2>\n')
if os.path.isfile(cwd + '/src/oai_rules_result.txt'):
cmd = 'grep NB_FILES_FAILING_CHECK ' + cwd + '/src/oai_rules_result.txt | sed -e "s#NB_FILES_FAILING_CHECK=##"'
nb_fail = subprocess.check_output(cmd, shell=True, universal_newlines=True)
cmd = 'grep NB_FILES_CHECKED ' + cwd + '/src/oai_rules_result.txt | sed -e "s#NB_FILES_CHECKED=##"'
nb_total = subprocess.check_output(cmd, shell=True, universal_newlines=True)
if int(nb_fail.strip()) == 0:
self.file.write(' <div class="alert alert-success">\n')
if self.git_pull_request:
self.file.write(' <strong>All modified files in Pull-Request follow OAI rules. <span class="glyphicon glyphicon-ok-circle"></span> -> (' + nb_total.strip() + ' were checked)</strong>\n')
else:
self.file.write(' <strong>All files in repository follow OAI rules. <span class="glyphicon glyphicon-ok-circle"></span> -> (' + nb_total.strip() + ' were checked)</strong>\n')
self.file.write(' </div>\n')
else:
self.file.write(' <div class="alert alert-warning">\n')
if self.git_pull_request:
self.file.write(' <strong>' + nb_fail.strip() + ' modified files in Pull-Request DO NOT follow OAI rules. <span class="glyphicon glyphicon-warning-sign"></span> -> (' + nb_total.strip() + ' were checked)</strong>\n')
else:
self.file.write(' <strong>' + nb_fail.strip() + ' files in repository DO NOT follow OAI rules. <span class="glyphicon glyphicon-warning-sign"></span> -> (' + nb_total.strip() + ' were checked)</strong>\n')
self.file.write(' </div>\n')
if os.path.isfile(cwd + '/src/oai_rules_result_list.txt'):
self.file.write(' <button data-toggle="collapse" data-target="#oai-formatting-details">More details on formatting check</button>\n')
self.file.write(' <div id="oai-formatting-details" class="collapse">\n')
self.file.write(' <p>Please apply the following command to this(ese) file(s): </p>\n')
self.file.write(' <p style="margin-left: 30px"><strong><code>cd src && clang-format -i filename(s)</code></strong></p>\n')
self.file.write(' <table class="table-bordered" width = "60%" align = "center" border = 1>\n')
self.file.write(' <tr><th bgcolor = "lightcyan" >Filename</th></tr>\n')
with open(cwd + '/src/oai_rules_result_list.txt', 'r') as filelist:
for line in filelist:
self.file.write(' <tr><td>' + line.strip() + '</td></tr>\n')
filelist.close()
self.file.write(' </table>\n')
self.file.write(' </div>\n')
else:
self.file.write(' <div class="alert alert-danger">\n')
self.file.write(' <strong>Was NOT performed (with CLANG-FORMAT tool). <span class="glyphicon glyphicon-ban-circle"></span></strong>\n')
self.file.write(' </div>\n')
self.file.write(' <br>\n')
def analyze_sca_log(self):
cwd = os.getcwd()
if os.path.isfile(cwd + '/archives/cppcheck_build.log'):
self.file.write(' <h2>Static Code Analysis</h2>\n')
if os.path.isfile(cwd + '/archives/cppcheck.xml'):
nb_errors = 0
nb_warnings = 0
nb_uninitvar = 0
nb_uninitStructMember = 0
nb_memleak = 0
nb_doubleFree = 0
nb_resourceLeak = 0
nb_nullPointer = 0
nb_arrayIndexOutOfBounds = 0
nb_bufferAccessOutOfBounds = 0
nb_unknownEvaluationOrder = 0
with open(cwd + '/archives/cppcheck.xml', 'r') as xmlfile:
for line in xmlfile:
result = re.search('severity="warning"', line)
if result is not None:
nb_warnings += 1
result = re.search('severity="error"', line)
if result is not None:
nb_errors += 1
result = re.search('uninitvar', line)
if result is not None:
nb_uninitvar += 1
result = re.search('uninitStructMember', line)
if result is not None:
nb_uninitStructMember += 1
result = re.search('memleak', line)
if result is not None:
nb_memleak += 1
result = re.search('doubleFree', line)
if result is not None:
nb_doubleFree += 1
result = re.search('resourceLeak', line)
if result is not None:
nb_resourceLeak += 1
result = re.search('nullPointer', line)
if result is not None:
nb_nullPointer += 1
result = re.search('arrayIndexOutOfBounds', line)
if result is not None:
nb_arrayIndexOutOfBounds += 1
result = re.search('bufferAccessOutOfBounds', line)
if result is not None:
nb_bufferAccessOutOfBounds += 1
result = re.search('unknownEvaluationOrder', line)
if result is not None:
nb_unknownEvaluationOrder += 1
xmlfile.close()
if (nb_errors == 0) and (nb_warnings == 0):
self.file.write(' <div class="alert alert-success">\n')
self.file.write(' <strong>CPPCHECK found NO error and NO warning <span class="glyphicon glyphicon-ok-circle"></span></strong>\n')
self.file.write(' </div>\n')
elif (nb_errors == 0):
self.file.write(' <div class="alert alert-warning">\n')
self.file.write(' <strong>CPPCHECK found NO error and ' + str(nb_warnings) + ' warnings <span class="glyphicon glyphicon-warning-sign"></span></strong>\n')
self.file.write(' </div>\n')
else:
self.file.write(' <div class="alert alert-danger">\n')
self.file.write(' <strong>CPPCHECK found ' + str(nb_errors) + ' errors and ' + str(nb_warnings) + ' warnings <span class="glyphicon glyphicon-ban-circle"></span></strong>\n')
self.file.write(' </div>\n')
if (nb_errors > 0) or (nb_warnings > 0):
self.file.write(' <button data-toggle="collapse" data-target="#oai-cppcheck-details">More details on CPPCHECK results</button>\n')
self.file.write(' <div id="oai-cppcheck-details" class="collapse">\n')
self.file.write(' <br>\n')
self.file.write(' <table class="table-bordered" width = "80%" align = "center" border = "1">\n')
self.file.write(' <tr bgcolor = "#33CCFF" >\n')
self.file.write(' <th>Error / Warning Type</th>\n')
self.file.write(' <th>Nb Errors</th>\n')
self.file.write(' <th>Nb Warnings</th>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Uninitialized variable</td>\n')
self.file.write(' <td>' + str(nb_uninitvar) + '</td>\n')
self.file.write(' <td>N/A</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Uninitialized struct member</td>\n')
self.file.write(' <td>' + str(nb_uninitStructMember) + '</td>\n')
self.file.write(' <td>N/A</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Memory leak</td>\n')
self.file.write(' <td>' + str(nb_memleak) + '</td>\n')
self.file.write(' <td>N/A</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Memory is freed twice</td>\n')
self.file.write(' <td>' + str(nb_doubleFree) + '</td>\n')
self.file.write(' <td>N/A</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Resource leak</td>\n')
self.file.write(' <td>' + str(nb_resourceLeak) + '</td>\n')
self.file.write(' <td>N/A</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Possible null pointer dereference</td>\n')
self.file.write(' <td>' + str(nb_nullPointer) + '</td>\n')
self.file.write(' <td>N/A</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Array access out of bounds</td>\n')
self.file.write(' <td>' + str(nb_arrayIndexOutOfBounds) + '</td>\n')
self.file.write(' <td>N/A</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Buffer is accessed out of bounds</td>\n')
self.file.write(' <td>' + str(nb_bufferAccessOutOfBounds) + '</td>\n')
self.file.write(' <td>N/A</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Expression depends on order of evaluation of side effects</td>\n')
self.file.write(' <td>' + str(nb_unknownEvaluationOrder) + '</td>\n')
self.file.write(' <td>N/A</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.file.write(' <td>Others</td>\n')
nb_others = nb_uninitvar + nb_uninitStructMember + nb_memleak + nb_doubleFree + nb_resourceLeak + nb_nullPointer + nb_arrayIndexOutOfBounds + nb_arrayIndexOutOfBounds + nb_bufferAccessOutOfBounds + nb_unknownEvaluationOrder
nb_others = nb_errors - nb_others
self.file.write(' <td>' + str(nb_others) + '</td>\n')
self.file.write(' <td>' + str(nb_warnings) + '</td>\n')
self.file.write(' </tr>\n')
self.file.write(' <tr bgcolor = "#33CCFF" >\n')
self.file.write(' <th>Total</th>\n')
self.file.write(' <th>' + str(nb_errors) + '</th>\n')
self.file.write(' <th>' + str(nb_warnings) + '</th>\n')
self.file.write(' </tr>\n')
self.file.write(' </table>\n')
self.file.write(' <br>\n')
self.file.write(' <p>Full details in artifact (cppcheck.xml) </p>\n')
self.file.write(' <p style="margin-left: 30px">Graphical Interface tool : <strong><code>cppcheck-gui -l cppcheck.xml</code></strong></p>\n')
self.file.write(' <br>\n')
self.file.write(' </div>\n')
else:
self.file.write(' <div class="alert alert-danger">\n')
self.file.write(' <strong>Was NOT performed (with CPPCHECK tool). <span class="glyphicon glyphicon-ban-circle"></span></strong>\n')
self.file.write(' </div>\n')
def buildSummaryHeader(self):
self.file.write(' <h2>Docker Image Build Summary</h2>\n')
self.file.write(' <table class="table-bordered" width = "100%" align = "center" border = "1">\n')
self.file.write(' <tr bgcolor="#33CCFF" >\n')
self.file.write(' <th>Stage Name</th>\n')
self.file.write(' <th>Image Kind</th>\n')
self.file.write(' <th>OAI NRF cNF</th>\n')
self.file.write(' </tr>\n')
def buildSummaryFooter(self):
self.file.write(' </table>\n')
self.file.write(' <br>\n')
if self.nb_warnings > 0:
self.file.write(' <h3>Compilation Warnings Details</h3>\n')
self.file.write(' <button data-toggle="collapse" data-target="#oai-compilation-details">Details for Compilation Errors and Warnings </button>\n')
self.file.write(' <div id="oai-compilation-details" class="collapse">\n')
self.file.write(' <table class="table-bordered">\n')
self.file.write(' <tr bgcolor = "#33CCFF" >\n')
self.file.write(' <th>File</th>\n')
self.file.write(' <th>Line Number</th>\n')
self.file.write(' <th>Status</th>\n')
self.file.write(' <th>Message</th>\n')
self.file.write(self.warning_rows)
self.file.write(' </tr>\n')
self.file.write(' </table>\n')
self.file.write(' </div>\n')
def initialGitSetup(self):
self.file.write(' <tr>\n')
self.file.write(' <td bgcolor="lightcyan" >Initial Git Setup</td>\n')
self.analyze_docker_build_git_part('NRF')
self.file.write(' </tr>\n')
def analyze_docker_build_git_part(self, nfType):
if nfType != 'NRF':
self.file.write(' <td>N/A</td>\n')
self.file.write(' <td>Wrong NF Type for this Report</td>\n')
return
logFileName = 'nrf_docker_image_build.log'
self.file.write(' <td>Builder Image</td>\n')
cwd = os.getcwd()
if os.path.isfile(cwd + '/archives/' + logFileName):
status = False
section_start_pattern = 'git config --global http'
section_end_pattern = 'WORKDIR /openair-nrf/build/scripts'
section_status = False
with open(cwd + '/archives/' + logFileName, 'r') as logfile:
for line in logfile:
result = re.search(section_start_pattern, line)
if result is not None:
section_status = True
result = re.search(section_end_pattern, line)
if result is not None:
section_status = False
status = True
logfile.close()
if status:
cell_msg = ' <td bgcolor="LimeGreen"><pre style="border:none; background-color:LimeGreen"><b>'
cell_msg += 'OK:\n'
cell_msg += ' -- All Git Operations went successfully</b></pre></td>\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO::\n'
cell_msg += ' -- Some Git Operations went WRONG</b></pre></td>\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO: logfile (' + logFileName + ') not found</b></pre></td>\n'
self.file.write(cell_msg)
def installLibsPackagesRow(self):
self.file.write(' <tr>\n')
self.file.write(' <td bgcolor="lightcyan" >SW libs and packages Installation</td>\n')
self.analyze_install_log('NRF')
self.file.write(' </tr>\n')
def analyze_install_log(self, nfType):
if nfType != 'NRF':
self.file.write(' <td>N/A</td>\n')
self.file.write(' <td>Wrong NF Type for this Report</td>\n')
return
logFileName = 'nrf_docker_image_build.log'
self.file.write(' <td>Builder Image</td>\n')
cwd = os.getcwd()
if os.path.isfile(cwd + '/archives/' + logFileName):
status = False
section_start_pattern = 'build_nrf --install-deps --force'
section_end_pattern = 'build_nrf --clean --Verbose --build-type Release --jobs'
section_status = False
package_install = False
fmt_build_start = False
fmt_build_status = False
folly_build_start = False
folly_build_status = False
spdlog_build_start = False
spdlog_build_status = False
pistache_build_start = False
pistache_build_status = False
json_build_start = False
json_build_status = False
with open(cwd + '/archives/' + logFileName, 'r') as logfile:
for line in logfile:
result = re.search(section_start_pattern, line)
if result is not None:
section_status = True
result = re.search(section_end_pattern, line)
if result is not None:
section_status = False
if section_status:
result = re.search('NRF deps installation successful', line)
if result is not None:
status = True
result = re.search('Install fmt from source', line)
if result is not None:
package_install = True
fmt_build_start = True
result = re.search('Installing: /usr/local/lib/pkgconfig/fmt.pc', line)
if result is not None:
fmt_build_status = True
result = re.search('Cloning into \'folly\'', line)
if result is not None:
folly_build_start = True
result = re.search('Installing: /usr/local/lib/libfollybenchmark.a', line)
if result is not None:
folly_build_status = True
result = re.search('Install spdlog from', line)
if result is not None:
spdlog_build_start = True
result = re.search('Install Pistache from', line)
if result is not None:
spdlog_build_status = True
pistache_build_start = True
result = re.search('Installing: /usr/local/lib/libpistache.a', line)
if result is not None:
pistache_build_status = True
result = re.search('Install Nlohmann Json', line)
if result is not None:
json_build_status = True
result = re.search('Installing: /usr/local/lib/cmake/nlohmann_json/nlohmann_jsonTargets.cmake', line)
if result is not None:
json_build_status = True
logfile.close()
if status:
cell_msg = ' <td bgcolor="LimeGreen"><pre style="border:none; background-color:LimeGreen"><b>'
cell_msg += 'OK:\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO:\n'
cell_msg += ' -- build_nrf --install-deps --force\n'
if package_install:
cell_msg += ' ** Packages Installation: OK\n'
else:
cell_msg += ' ** Packages Installation: KO\n'
if fmt_build_status:
cell_msg += ' ** fmt Installation: OK\n'
else:
cell_msg += ' ** fmt Installation: KO\n'
if folly_build_status:
cell_msg += ' ** folly Installation: OK\n'
else:
cell_msg += ' ** folly Installation: KO\n'
if spdlog_build_status:
cell_msg += ' ** spdlog Installation: OK\n'
else:
cell_msg += ' ** spdlog Installation: KO\n'
if pistache_build_status:
cell_msg += ' ** pistache Installation: OK\n'
else:
cell_msg += ' ** pistache Installation: KO\n'
if json_build_status:
cell_msg += ' ** Nlohmann Json Installation: OK\n'
else:
cell_msg += ' ** Nlohmann Json Installation: KO\n'
cell_msg += '</b></pre></td>\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO: logfile (' + logFileName + ') not found</b></pre></td>\n'
self.file.write(cell_msg)
def buildCompileRows(self):
self.file.write(' <tr>\n')
self.file.write(' <td rowspan=2 bgcolor="lightcyan" >cNF Compile / Build</td>\n')
self.analyze_build_log('NRF', True)
self.file.write(' </tr>\n')
self.file.write(' <tr>\n')
self.analyze_compile_log('NRF', True)
self.file.write(' </tr>\n')
def analyze_build_log(self, nfType, imageKind):
if nfType != 'NRF':
if imageKind:
self.file.write(' <td>N/A</td>\n')
self.file.write(' <td>Wrong NF Type for this Report</td>\n')
return
if nfType == 'NRF':
logFileName = 'nrf_docker_image_build.log'
if imageKind:
self.file.write(' <td>Builder Image</td>\n')
cwd = os.getcwd()
if os.path.isfile(cwd + '/archives/' + logFileName):
status = False
if nfType == 'NRF':
section_start_pattern = 'build_nrf --clean --Verbose --build-type Release --jobs'
section_end_pattern = 'FROM ubuntu:bionic as oai-nrf$'
pass_pattern = 'nrf installed'
section_status = False
with open(cwd + '/archives/' + logFileName, 'r') as logfile:
for line in logfile:
result = re.search(section_start_pattern, line)
if result is not None:
section_status = True
result = re.search(section_end_pattern, line)
if result is not None:
section_status = False
if section_status:
result = re.search(pass_pattern, line)
if result is not None:
status = True
logfile.close()
if status:
cell_msg = ' <td bgcolor="LimeGreen"><pre style="border:none; background-color:LimeGreen"><b>'
cell_msg += 'OK:\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO:\n'
cell_msg += ' -- ' + section_start_pattern + '</b></pre></td>\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO: logfile (' + logFileName + ') not found</b></pre></td>\n'
self.file.write(cell_msg)
def analyze_compile_log(self, nfType, imageKind):
if nfType != 'NRF':
if imageKind:
self.file.write(' <td>N/A</td>\n')
self.file.write(' <td>Wrong NF Type for this Report</td>\n')
return
if nfType == 'NRF':
logFileName = 'nrf_docker_image_build.log'
if imageKind:
self.file.write(' <td>Builder Image</td>\n')
cwd = os.getcwd()
nb_errors = 0
nb_warnings = 0
if os.path.isfile(cwd + '/archives/' + logFileName):
if nfType == 'NRF':
section_start_pattern = 'build_nrf --clean --Verbose --build-type Release --jobs'
section_end_pattern = 'FROM ubuntu:bionic as oai-nrf$'
section_status = False
with open(cwd + '/archives/' + logFileName, 'r') as logfile:
for line in logfile:
result = re.search(section_start_pattern, line)
if result is not None:
section_status = True
result = re.search(section_end_pattern, line)
if result is not None:
section_status = False
if section_status:
result = re.search('error:', line)
if result is not None:
nb_errors += 1
result = re.search('warning:', line)
if result is not None:
correctLine = re.sub("^.*/openair-nrf","/openair-nrf",line.strip())
wordsList = correctLine.split(None,2)
filename = re.sub(":[0-9]*:[0-9]*:","", wordsList[0])
linenumber = re.sub(filename + ':',"", wordsList[0])
linenumber = re.sub(':[0-9]*:',"", linenumber)
error_warning_status = re.sub(':',"", wordsList[1])
error_warning_msg = re.sub('^.*' + error_warning_status + ':', '', correctLine)
nb_warnings += 1
self.warning_rows += '<tr><td>' + filename + '</td><td>' + linenumber + '</td><td>' + error_warning_status + '</td><td>' + error_warning_msg + '</td></tr>\n'
logfile.close()
if nb_warnings == 0 and nb_errors == 0:
cell_msg = ' <td bgcolor="LimeGreen"><pre style="border:none; background-color:LimeGreen"><b>'
elif nb_warnings < 20 and nb_errors == 0:
cell_msg = ' <td bgcolor="Orange"><pre style="border:none; background-color:Orange"><b>'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
if nb_errors > 0:
cell_msg += str(nb_errors) + ' errors found in compile log\n'
cell_msg += str(nb_warnings) + ' warnings found in compile log</b></pre></td>\n'
self.nb_warnings = nb_warnings
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO: logfile (' + logFileName + ') not found</b></pre></td>\n'
self.file.write(cell_msg)
def copyToTargetImage(self):
self.file.write(' <tr>\n')
self.file.write(' <td bgcolor="lightcyan" >SW libs Installation / Copy from Builder</td>\n')
self.analyze_copy_log('NRF')
self.file.write(' </tr>\n')
def analyze_copy_log(self, nfType):
if nfType != 'NRF':
self.file.write(' <td>N/A</td>\n')
self.file.write(' <td>Wrong NF Type for this Report</td>\n')
return
logFileName = 'nrf_docker_image_build.log'
self.file.write(' <td>Target Image</td>\n')
cwd = os.getcwd()
if os.path.isfile(cwd + '/archives/' + logFileName):
section_start_pattern = 'FROM ubuntu:bionic as oai-nrf$'
section_end_pattern = 'WORKDIR /openair-nrf/etc'
section_status = False
status = False
with open(cwd + '/archives/' + logFileName, 'r') as logfile:
for line in logfile:
result = re.search(section_start_pattern, line)
if result is not None:
section_status = True
result = re.search(section_end_pattern, line)
if result is not None:
section_status = False
status = True
logfile.close()
if status:
cell_msg = ' <td bgcolor="LimeGreen"><pre style="border:none; background-color:LimeGreen"><b>'
cell_msg += 'OK:\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO:\n'
cell_msg += '</b></pre></td>\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO: logfile (' + logFileName + ') not found</b></pre></td>\n'
self.file.write(cell_msg)
def copyConfToolsToTargetImage(self):
self.file.write(' <tr>\n')
self.file.write(' <td bgcolor="lightcyan" >Copy Template Conf / Tools from Builder</td>\n')
self.analyze_copy_conf_tool_log('NRF')
self.file.write(' </tr>\n')
def analyze_copy_conf_tool_log(self, nfType):
if nfType != 'NRF':
self.file.write(' <td>N/A</td>\n')
self.file.write(' <td>Wrong NF Type for this Report</td>\n')
return
logFileName = 'nrf_docker_image_build.log'
self.file.write(' <td>Target Image</td>\n')
cwd = os.getcwd()
if os.path.isfile(cwd + '/archives/' + logFileName):
section_start_pattern = 'WORKDIR /openair-nrf/etc'
section_end_pattern = 'Successfully tagged oai-nrf'
section_status = False
status = False
with open(cwd + '/archives/' + logFileName, 'r') as logfile:
for line in logfile:
result = re.search(section_start_pattern, line)
if result is not None:
section_status = True
result = re.search(section_end_pattern, line)
if result is not None:
section_status = False
status = True
logfile.close()
if status:
cell_msg = ' <td bgcolor="LimeGreen"><pre style="border:none; background-color:LimeGreen"><b>'
cell_msg += 'OK:\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO:\n'
cell_msg += '</b></pre></td>\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO: logfile (' + logFileName + ') not found</b></pre></td>\n'
self.file.write(cell_msg)
def imageSizeRow(self):
self.file.write(' <tr>\n')
self.file.write(' <td bgcolor="lightcyan" >Image Size</td>\n')
self.analyze_image_size_log('NRF', True)
self.file.write(' </tr>\n')
def analyze_image_size_log(self, nfType, imageKind):
if nfType != 'NRF':
if imageKind:
self.file.write(' <td>N/A</td>\n')
self.file.write(' <td>Wrong NF Type for this Report</td>\n')
return
if nfType == 'NRF':
logFileName = 'nrf_docker_image_build.log'
if imageKind:
self.file.write(' <td>Target Image</td>\n')
cwd = os.getcwd()
if os.path.isfile(cwd + '/archives/' + logFileName):
if nfType == 'NRF':
section_start_pattern = 'Successfully tagged oai-nrf'
section_end_pattern = 'OAI-SMF DOCKER IMAGE BUILD'
section_status = False
status = False
with open(cwd + '/archives/' + logFileName, 'r') as logfile:
for line in logfile:
result = re.search(section_start_pattern, line)
if result is not None:
section_status = True
result = re.search(section_end_pattern, line)
if result is not None:
section_status = False
if section_status:
if nfType == 'NRF':
if self.git_pull_request:
result = re.search('oai-nrf *ci-temp', line)
else:
result = re.search('oai-nrf *develop', line)
if result is not None:
result = re.search('ago *([0-9A-Z]+)', line)
if result is not None:
size = result.group(1)
status = True
logfile.close()
if status:
cell_msg = ' <td bgcolor="LimeGreen"><pre style="border:none; background-color:LimeGreen"><b>'
cell_msg += 'OK: ' + size + '\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO:\n'
cell_msg += '</b></pre></td>\n'
else:
cell_msg = ' <td bgcolor="Tomato"><pre style="border:none; background-color:Tomato"><b>'
cell_msg += 'KO: logfile (' + logFileName + ') not found</b></pre></td>\n'
self.file.write(cell_msg)
def Usage():
print('----------------------------------------------------------------------------------------------------------------------')
print('generateHtmlReport.py')
print(' Generate an HTML report for the Jenkins pipeline on openair-nrf.')
print('----------------------------------------------------------------------------------------------------------------------')
print('Usage: python3 generateHtmlReport.py [options]')
print(' --help Show this help.')
print('---------------------------------------------------------------------------------------------- Mandatory Options -----')
print(' --job_name=[Jenkins Job name]')
print(' --job_id=[Jenkins Job Build ID]')
print(' --job_url=[Jenkins Job Build URL]')
print(' --git_url=[Git Repository URL]')
print(' --git_src_branch=[Git Source Branch Name]')
print(' --git_src_commit=[Git Source Commit SHA-ONE]')
print('----------------------------------------------------------------------------------------------- Optional Options -----')
print(' --git_pull_request=True')
print(' --git_target_branch=[Git Target Branch Name]')
print(' --git_target_commit=[Git Target Commit SHA-ONE]')
#--------------------------------------------------------------------------------------------------------
#
# Start of main
#
#--------------------------------------------------------------------------------------------------------
argvs = sys.argv
argc = len(argvs)
HTML = HtmlReport()
while len(argvs) > 1:
myArgv = argvs.pop(1)
if re.match('^\-\-help$', myArgv, re.IGNORECASE):
Usage()
sys.exit(0)
elif re.match('^\-\-job_name=(.+)$', myArgv, re.IGNORECASE):
matchReg = re.match('^\-\-job_name=(.+)$', myArgv, re.IGNORECASE)
HTML.job_name = matchReg.group(1)
elif re.match('^\-\-job_id=(.+)$', myArgv, re.IGNORECASE):
matchReg = re.match('^\-\-job_id=(.+)$', myArgv, re.IGNORECASE)
HTML.job_id = matchReg.group(1)
elif re.match('^\-\-job_url=(.+)$', myArgv, re.IGNORECASE):
matchReg = re.match('^\-\-job_url=(.+)$', myArgv, re.IGNORECASE)
HTML.job_url = matchReg.group(1)
elif re.match('^\-\-git_url=(.+)$', myArgv, re.IGNORECASE):
matchReg = re.match('^\-\-git_url=(.+)$', myArgv, re.IGNORECASE)
HTML.git_url = matchReg.group(1)
elif re.match('^\-\-git_src_branch=(.+)$', myArgv, re.IGNORECASE):
matchReg = re.match('^\-\-git_src_branch=(.+)$', myArgv, re.IGNORECASE)
HTML.git_src_branch = matchReg.group(1)
elif re.match('^\-\-git_src_commit=(.+)$', myArgv, re.IGNORECASE):
matchReg = re.match('^\-\-git_src_commit=(.+)$', myArgv, re.IGNORECASE)
HTML.git_src_commit = matchReg.group(1)
elif re.match('^\-\-git_src_commit_msg=(.+)$', myArgv, re.IGNORECASE):
# Not Mandatory
matchReg = re.match('^\-\-git_src_commit_msg=(.+)$', myArgv, re.IGNORECASE)
HTML.git_src_commit_msg = matchReg.group(1)
elif re.match('^\-\-git_pull_request=(.+)$', myArgv, re.IGNORECASE):
# Can be silent: would be false!
matchReg = re.match('^\-\-git_pull_request=(.+)$', myArgv, re.IGNORECASE)
if matchReg.group(1) == 'true' or matchReg.group(1) == 'True':
HTML.git_pull_request = True
elif re.match('^\-\-git_target_branch=(.+)$', myArgv, re.IGNORECASE):
matchReg = re.match('^\-\-git_target_branch=(.+)$', myArgv, re.IGNORECASE)
HTML.git_target_branch = matchReg.group(1)
elif re.match('^\-\-git_target_commit=(.+)$', myArgv, re.IGNORECASE):
matchReg = re.match('^\-\-git_target_commit=(.+)$', myArgv, re.IGNORECASE)
HTML.git_target_commit = matchReg.group(1)
else:
sys.exit('Invalid Parameter: ' + myArgv)
if HTML.job_name == '' or HTML.job_id == '' or HTML.job_url == '':
sys.exit('Missing Parameter in job description')
if HTML.git_url == '' or HTML.git_src_branch == '' or HTML.git_src_commit == '':
sys.exit('Missing Parameter in Git Repository description')
if HTML.git_pull_request:
if HTML.git_target_commit == '' or HTML.git_target_branch == '':
sys.exit('Missing Parameter in Git Pull Request Repository description')
HTML.generate()
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