#!/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 // Its main purpose is the Ubuntu Build ubuntuNode = params.UbuntuBuildNode ubuntuBuildResource = params.UbuntuBuildResource // Location of the RHEL CN executor rhelNode = params.RhelBuildNode rhelResource = params.RhelBuildResource rhelOcCredentials = params.RhelOcCredentials // Location of the CPPCHECK executor cppcheckNode = params.CppCheckNode cppcheckResource = params.CppCheckResource // Location of the CLANG-FORMAT-CHECK executor formatCheckNode = params.FormatCheckNode formatCheckResource = params.FormatCheckResource // Tags/Branches to use def pcf_tag = "develop" def pcf_branch = "develop" // Merge Request Link gitlabMergeRequestLink = '' gitCommittorEmailAddr = '' // Docker Hub account to push to DH_Account = "oaisoftwarealliance" // Private Local Registry URL PrivateRegistryURL = 'selfix.sboai.cs.eurecom.fr' //------------------------------------------------------------------------------- // Pipeline start pipeline { agent { label ubuntuNode } options { disableConcurrentBuilds() timestamps() ansiColor('xterm') gitLabConnection('OAI GitLab') // Minimal checks gitlabBuilds(builds: [ "Build Ubuntu PCF Image", "Build RHEL PCF 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.RhelOcCredentials == null) { echo '\u26D4 \u001B[31mNo Credentials to connect to Openshift!\u001B[0m' error "Stopping pipeline!" } if (params.DockerHubCredentials == null) { echo '\u26D4 \u001B[31mNo Credentials to push to DockerHub!\u001B[0m' error "Stopping pipeline!" } } } } stage ('Prepare Source Code') { steps { script { if ("MERGE".equals(env.gitlabActionType)) { gitlabMergeRequestLink = sh returnStdout: true, script: "curl --silent 'https://gitlab.eurecom.fr/api/v4/projects/oai%2Fcn5g%2Foai-cn5g-pcf/merge_requests/${env.gitlabMergeRequestIid}' | jq .web_url | sed 's#\"##g'" gitlabMergeRequestLink = gitlabMergeRequestLink.trim() gitCommittorEmailAddr = env.gitlabUserEmail shortenShaOne = sh returnStdout: true, script: 'git log -1 --pretty=format:"%h" --abbrev=8 ' + env.gitlabMergeRequestLastCommit shortenShaOne = shortenShaOne.trim() pcf_tag = 'ci-tmp-pr-' + env.gitlabMergeRequestIid + '-' + shortenShaOne pcf_branch = env.gitlabSourceBranch echo "========= THIS IS A MERGE REQUEST ==========" echo "MR ID is ${env.gitlabMergeRequestIid}" echo "MR LINK is ${gitlabMergeRequestLink}" echo "MR TITLE is ${env.gitlabMergeRequestTitle}" echo "MR Usermail is ${gitCommittorEmailAddr}" echo "MR TAG is ${pcf_tag}" } else { gitCommittorEmailAddr = sh returnStdout: true, script: 'git log -n1 --pretty=format:%ae ${GIT_COMMIT}' gitCommittorEmailAddr = gitCommittorEmailAddr.trim() shortenShaOne = sh returnStdout: true, script: 'git log -1 --pretty=format:"%h" --abbrev=8 ' + env.GIT_COMMIT shortenShaOne = shortenShaOne.trim() pcf_tag = 'develop-' + shortenShaOne pcf_branch = env.GIT_COMMIT echo "======== THIS IS A PUSH REQUEST ========" echo "Git Branch is ${GIT_BRANCH}" echo "Git Commit is ${GIT_COMMIT}" echo "CI Usermail is ${gitCommittorEmailAddr}" echo "CI develop TAG is ${pcf_tag}" } prepareWorkspaceMergeCase() } } 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 Ubuntu PCF Image') { steps { // Now it is only locked during this build stage and not for the whole pipeline lock(ubuntuBuildResource) { script { gitlabCommitStatus(name: "Build Ubuntu PCF Image") { sh "docker image rm oai-pcf:${pcf_tag} || true" sh "docker image prune --force" if ("PUSH".equals(env.gitlabActionType)) { dockerBuildOptions = '--no-cache ' } if ("MERGE".equals(env.gitlabActionType)) { dockerBuildOptions = '' } sh "docker buildx build ${dockerBuildOptions} --target oai-pcf --tag oai-pcf:${pcf_tag} --file docker/Dockerfile.pcf.ubuntu . > archives/pcf_ubuntu_image_build.log 2>&1" // Putting a place holder to try out on the flattening of image. // If not satisfactory, we can remove it. sh "python3 ./ci-scripts/flatten_image.py --tag oai-pcf:${pcf_tag}" sh "docker image prune --force" sh "docker image ls | egrep --color=never 'pcf|REPOSITORY' >> archives/pcf_ubuntu_image_build.log" // Pushing to local private registry for testing purpose sh "docker login -u oaicicd -p oaicicd ${PrivateRegistryURL}" sh "docker image tag oai-pcf:${pcf_tag} ${PrivateRegistryURL}/oai-pcf:${pcf_tag}" sh "docker push ${PrivateRegistryURL}/oai-pcf:${pcf_tag}" // Remove all images locally sh "docker rmi oai-pcf:${pcf_tag} ${PrivateRegistryURL}/oai-pcf:${pcf_tag}" sh "docker logout ${PrivateRegistryURL}" } } } } post { success { sh "echo 'OAI-PCF UBUNTU IMAGE BUILD: OK' >> archives/pcf_ubuntu_image_build.log" } unsuccessful { sh "echo 'OAI-PCF UBUNTU IMAGE BUILD: KO' >> archives/pcf_ubuntu_image_build.log" } } } stage ('Build RHEL PCF Image') { agent { label rhelNode } steps { lock (rhelResource) { script { gitlabCommitStatus(name: "Build RHEL PCF Image") { // It's a different agent from main one. prepareWorkspaceMergeCase() withCredentials([ [$class: 'UsernamePasswordMultiBinding', credentialsId: "${rhelOcCredentials}", usernameVariable: 'OC_Username', passwordVariable: 'OC_Password'] ]) { sh "oc login -u ${OC_Username} -p ${OC_Password}" } sh "oc delete istag oai-pcf:${pcf_tag} || true" // Copy the RHEL Host certificates for building sh "./ci-scripts/common/python/recreate_entitlement.py" // Building sh "oc delete -f openshift/build-config.yaml || true" sh "sed -i -e 's@oai-pcf:latest@oai-pcf:${pcf_tag}@g' openshift/build-config.yaml" sh "oc create -f openshift/build-config.yaml" sh 'oc start-build pcf-build-cfg --from-dir=./ --exclude=""' // need python to wait for pod pcf-build-cfg-1-build to be Completed or Error // it fails if it detects error or timeout at 20 minutes sh "./ci-scripts/common/python/check_build_pod_status.py --pod-name pcf-build-cfg-1-build --log-file archives/pcf_rhel_image_build.log" sh "oc describe istag oai-pcf:${pcf_tag} | grep 'Image Size:' >> archives/pcf_rhel_image_build.log" } } } } post { success { sh "echo 'OAI-PCF RHEL IMAGE BUILD: OK' >> archives/pcf_rhel_image_build.log" } unsuccessful { sh "echo 'OAI-PCF RHEL IMAGE BUILD: KO' >> archives/pcf_rhel_image_build.log" } cleanup { script { sh "oc delete build pcf-build-cfg-1 || true" sh "oc logout || true" stash allowEmpty: true, includes: 'archives/pcf_rhel_image_build.log', name: 'rhelBuildLog' } } } } // Running CPPCHECK in parallel to gain time stage ('Static Code Analysis') { agent { label cppcheckNode } steps { lock (cppcheckResource) { script { gitlabCommitStatus(name: "Static Code Analysis") { // It's a different agent from main one. prepareWorkspaceMergeCase() // Moving to focal and cppcheck 1.90 and a dockerfile approach sh 'sed -i -e "s@nfName@pcf@" ci-scripts/common/docker/Dockerfile.ci.cppcheck' sh 'docker build --target pcf-cppcheck --tag pcf-cppcheck:test --file ci-scripts/common/docker/Dockerfile.ci.cppcheck . > archives/cppcheck_install.log 2>&1' sh 'docker run --name pcf-ci-cppcheck --entrypoint /bin/true pcf-cppcheck:test' sh 'docker cp pcf-ci-cppcheck:/home/cppcheck.xml archives' sh 'docker cp pcf-ci-cppcheck:/home/cppcheck_build.log archives' sh 'docker rm -f pcf-ci-cppcheck' sh 'docker rmi pcf-cppcheck:test' } } } } post { success { sh "echo 'CPPCHECK: OK' >> archives/cppcheck_install.log" } unsuccessful { sh "echo 'CPPCHECK: KO' >> archives/cppcheck_install.log" } cleanup { script { stash allowEmpty: true, includes: 'archives/cppcheck*.*', name: 'cppcheckLogs' // no need to keep the cppcheck container sh 'docker rm -f pcf-ci-cppcheck || true' sh 'docker rmi pcf-cppcheck:test || true' } } } } // Running CLANG-FORMATTING check in parallel to gain time stage ('Code Formatting Checker') { agent { label formatCheckNode } steps { lock (formatCheckResource) { script { gitlabCommitStatus(name: "Code Formatting Checker") { // It's a different agent from main one. prepareWorkspaceMergeCase() sh 'sed -i -e "s@nfName@pcf@" ci-scripts/common/docker/Dockerfile.ci.clang-format' if ("MERGE".equals(env.gitlabActionType)) { sh 'docker build --target pcf-clang-format-check --tag pcf-clang-format-check:test --file ci-scripts/common/docker/Dockerfile.ci.clang-format --build-arg MERGE_REQUEST_CHECK=True --build-arg SOURCE_BRANCH=' + env.gitlabSourceBranch + ' --build-arg TARGET_BRANCH=' + env.gitlabTargetBranch + ' . > archives/clang_format_install.log 2>&1' } else { sh 'docker build --target pcf-clang-format-check --tag pcf-clang-format-check:test --file ci-scripts/common/docker/Dockerfile.ci.clang-format . > archives/clang_format_install.log 2>&1' } sh 'docker run --name pcf-ci-clang-format --entrypoint /bin/true pcf-clang-format-check:test' sh 'docker cp pcf-ci-clang-format:/home/src/oai_rules_result.txt src' sh 'docker cp pcf-ci-clang-format:/home/src/oai_rules_result_list.txt src || true' sh 'docker rm -f pcf-ci-clang-format' sh 'docker rmi pcf-clang-format-check:test' // The check is done now here sh 'grep -L "NB_FILES_FAILING_CHECK=0" src/oai_rules_result.txt' } } } } post { cleanup { script { stash allowEmpty: true, includes: 'src/oai_rules_result*.txt, archives/clang_format_install.log', name: 'formatCheckLogs' sh 'docker rm -f pcf-ci-clang-format || true' sh 'docker rmi pcf-clang-format-check:test || true' } } } } } post { always { script { unstash 'rhelBuildLog' unstash 'cppcheckLogs' unstash 'formatCheckLogs' } } } } stage('Testing whole 5g Core Network Functions') { parallel { stage ('Testing the tutorials') { steps { script { gitlabCommitStatus(name: "Test tutorials") { localStatus = build job: 'OAI-CN5G-Tutorials-Check', parameters: [ string(name: 'PCF_TAG', value: String.valueOf(pcf_tag)), string(name: 'PCF_BRANCH', value: String.valueOf(pcf_branch)) ], propagate: false localResult = localStatus.getResult() if (localStatus.resultIsBetterOrEqualTo('SUCCESS')) { echo "Tutorials Test Job is OK" } else { error "Tutorials Test Job is KO" } } } } post { always { script { copyArtifacts(projectName: 'OAI-CN5G-Tutorials-Check', filter: '*_results_oai_cn5g*.html', selector: lastCompleted()) } } } } // Home-made RAN emulator stage ('Robot-Test') { steps { script { gitlabCommitStatus(name: "Robot-Test") { localStatus = build job: 'OAI-CN5G-RobotTest', parameters: [ string(name: 'PCF_TAG', value: String.valueOf(pcf_tag)), string(name: 'PCF_BRANCH', value: String.valueOf(pcf_branch)) ], propagate: false localResult = localStatus.getResult() if (localStatus.resultIsBetterOrEqualTo('SUCCESS')) { echo "Robot-Test is OK" } else { error "Robot-Test is is KO" } } } } post { always { script { copyArtifacts(projectName: 'OAI-CN5G-RobotTest', filter: '*.html', selector: lastCompleted()) } } } } } } // We are only publishing the Ubuntu image to Docker-Hub // For Post-Merge events. // Temporary Images from Merge-Request Runs are kept in local private registry stage ('Pushing Image to Official Registry') { steps { lock(ubuntuBuildResource) { script { // Only in case of push to target branch! if ("PUSH".equals(env.gitlabActionType)) { withCredentials([ [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.DockerHubCredentials}", usernameVariable: 'DH_Username', passwordVariable: 'DH_Password'] ]) { sh "echo ${DH_Password} | docker login --username ${DH_Username} --password-stdin" } sh "docker login -u oaicicd -p oaicicd ${PrivateRegistryURL}" sh "docker pull ${PrivateRegistryURL}/oai-pcf:${pcf_tag}" sh "docker image tag ${PrivateRegistryURL}/oai-pcf:${pcf_tag} ${DH_Account}/oai-pcf:develop" sh "docker push ${DH_Account}/oai-pcf:develop" sh "docker rmi ${DH_Account}/oai-pcf:develop ${PrivateRegistryURL}/oai-pcf:${pcf_tag}" sh "docker logout ${PrivateRegistryURL}" sh "docker logout" } } } } } } 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 { // 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(s) if ("MERGE".equals(env.gitlabActionType)) { sh "./ci-scripts/generateHtmlReport.py --job-name ${JOB_NAME} --build-id ${BUILD_ID} --build-url ${BUILD_URL} --git-url ${GIT_URL} --git-src-branch ${env.gitlabSourceBranch} --git-src-commit ${env.gitlabMergeRequestLastCommit} --git-merge-request --git-dst-branch ${env.gitlabTargetBranch} --git-dst-commit ${GIT_COMMIT}" } else { sh "./ci-scripts/generateHtmlReport.py --job-name ${JOB_NAME} --build-id ${BUILD_ID} --build-url ${BUILD_URL} --git-url ${GIT_URL} --git-src-branch ${GIT_BRANCH} --git-src-commit ${GIT_COMMIT}" } listOfFiles = sh returnStdout: true, script: 'ls test_results*.html' String[] htmlFiles = listOfFiles.split("\\n") for (htmlFile in htmlFiles) { if ("MERGE".equals(env.gitlabActionType)) { sh "sed -i -e 's#TEMPLATE_MERGE_REQUEST_LINK#${gitlabMergeRequestLink}#g' ${htmlFile}" sh "sed -i -e 's#TEMPLATE_MERGE_REQUEST_TEMPLATE#${env.gitlabMergeRequestTitle}#' ${htmlFile}" } sh "sed -i -e 's#TEMPLATE_TIME#${JOB_TIMESTAMP}#' ${htmlFile}" archiveArtifacts artifacts: htmlFile } // 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: gitCommittorEmailAddr } } } } } } def prepareWorkspaceMergeCase () { sh "git clean -x -d -f > /dev/null 2>&1" sh "git submodule foreach --recursive 'git clean -x -d -ff' > /dev/null 2>&1" sh "git submodule deinit --force --all > /dev/null 2>&1" if ("MERGE".equals(env.gitlabActionType)) { sh "./ci-scripts/doGitLabMerge.sh --src-branch ${env.gitlabSourceBranch} --src-commit ${env.gitlabMergeRequestLastCommit} --target-branch ${env.gitlabTargetBranch} --target-commit ${GIT_COMMIT}" } sh "git submodule update --init --recursive" sh "mkdir -p archives" }