blob: 914dbbb6f53da4cc8c14a315a401ce8db4f0bf64 [file] [log] [blame]
/*
Copyright (c) 2022 Nordix Foundation
Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
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.
*/
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
pending = 'PENDING'
success = 'SUCCESS'
failure = 'FAILURE'
base_image = 'base-image'
in_progress = 'In Progress.'
completed = 'Completed.'
failed = 'Failed'
exception_message_exec = 'failed to execute the following command: '
exception_message_code_generation = 'Generated code verification failed'
node('nordix-nsm-build-ubuntu2204') {
build_number = env.BUILD_NUMBER
workspace = env.WORKSPACE
ws("${workspace}/${build_number}") {
def image_names = params.IMAGE_NAMES.split(' ')
def version = params.IMAGE_VERSION
def e2e_enabled = params.E2E_ENABLED
def helm_chart_upload = params.HELM_CHART_UPLOAD
def security_scan_enabled = params.SECURITY_SCAN_ENABLED
def git_project = params.GIT_PROJECT
def current_branch = params.CURRENT_BRANCH
def default_branch = params.DEFAULT_BRANCH
def build_steps = params.BUILD_STEPS
def image_registry = params.IMAGE_REGISTRY
def local_version = "${env.JOB_NAME}-${build_number}"
timeout(30) {
stage('Clone/Checkout') {
git branch: default_branch, url: git_project
checkout([
$class: 'GitSCM',
branches: [[name: current_branch]],
extensions: [],
userRemoteConfigs: [[
refspec: '+refs/pull/*/head:refs/remotes/origin/pr/*',
url: git_project
]]
])
sh 'git show'
}
Verify().call()
stage('Docker login') {
if (env.DRY_RUN != 'true') {
withCredentials([usernamePassword(credentialsId: 'nordix-cicd-harbor-credentials', passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USERNAME')]) {
sh '''#!/bin/bash -eu
echo $HARBOR_PASSWORD | docker login --username $HARBOR_USERNAME --password-stdin $IMAGE_REGISTRY
'''
}
} else {
echo 'Docker login'
}
}
stage('Base Image') {
BaseImage(version, build_steps, image_registry, local_version).call()
}
stage('Images') {
Images(image_names, version, build_steps, image_registry, local_version).call()
if (currentBuild.result == 'FAILURE') {
Error('Failed to build image(s)').call()
}
}
stage('Helm Chart') {
HelmChart(helm_chart_upload, version).call()
}
stage('Security Scan') {
if (security_scan_enabled == true) {
SecurityScan(current_branch, version).call()
} else {
Utils.markStageSkippedForConditional('Security Scan')
}
}
stage('E2E') {
if (e2e_enabled == true) {
E2e(current_branch, version).call()
} else {
Utils.markStageSkippedForConditional('E2E')
}
}
}
stage('Cleanup') {
Cleanup()
}
}
}
// Verify the Generated code, UnitTests and Linter
def Verify() {
return {
GeneratedCode().call() // cannot generate code and run the linter and tests at the same time
Linter().call()
UnitTests().call()
}
}
// Runs the unit tests and set the github commit status
def UnitTests() {
return {
def context = 'Unit Tests'
stage('Unit Tests') {
def command = 'make test'
try {
SetBuildStatus(in_progress, context, pending)
ExecSh(command).call()
SetBuildStatus(completed, context, success)
} catch (Exception e) {
SetBuildStatus(failed, context, failure)
Error("${exception_message_exec} ${command}").call()
}
}
}
}
// Runs the linter and set the github commit status
def Linter() {
return {
def context = 'Linter'
stage('Linter') {
def command = 'make lint'
try {
SetBuildStatus(in_progress, context, pending)
ExecSh(command).call()
SetBuildStatus(completed, context, success)
} catch (Exception e) {
SetBuildStatus(failed, context, failure)
Error("${exception_message_exec} ${command}").call()
}
}
}
}
// Check if code has been generated correctly and set the github commit status:
// go.mod: runs "go mod tidy"
// go generate ./...: Code should be generated using "make genrate" command
// proto: skipped due to version of protoc
// If files are generated correctly then GetModifiedFiles function should return an empty string
def GeneratedCode() {
return {
def context = 'Generated code verification'
stage('Generated code verification') {
def command = 'make go-generate manifests generate-controller'
try {
SetBuildStatus(in_progress, context, pending)
ExecSh(command).call()
if (GetModifiedFiles() != '') {
throw new Exception(exception_message_code_generation)
}
SetBuildStatus(completed, context, success)
} catch (Exception e) {
SetBuildStatus(failed, context, failure)
Error(exception_message_exec + command).call()
}
}
}
}
def BaseImage(version, build_steps, registry, local_version) {
return {
Build(base_image, version, build_steps, registry, local_version).call()
}
}
// Call Build function for every images in parallel
def Images(images, version, build_steps, registry, local_version) {
return {
def stages = [:]
for (i in images) {
stages.put(i, Build(i, version, build_steps, registry, local_version))
}
parallel(stages)
}
}
// Build set the github commit status
def Build(image, version, build_steps, registry, local_version) {
return {
stage("${image} (${version}): ${build_steps}") {
def context = "Image: ${image}"
def in_progress_message = "${in_progress} (${build_steps})"
def completed_message = "${completed} (${build_steps})"
def failed_message = "${failed} (${build_steps})"
def command = "make ${image} VERSION=${version} BUILD_STEPS='${build_steps}' REGISTRY=${registry} LOCAL_VERSION=${local_version} BASE_IMAGE=${base_image}:${local_version}"
try {
SetBuildStatus(in_progress_message, context, pending)
ExecSh(command).call()
SetBuildStatus(completed_message, context, success)
} catch (Exception e) {
SetBuildStatus(failed_message, context, failure)
unstable "${exception_message_exec} ${command}"
currentBuild.result = 'FAILURE'
}
}
}
}
// Generate and upload the helm chart
def HelmChart(helm_chart_upload, version) {
return {
parallel(
'Helm Chart': {
stage('Generate Helm Chart') {
def context = 'Generate Helm Chart'
def command = "make generate-helm-chart VERSION=${version}"
try {
SetBuildStatus(in_progress, context, pending)
ExecSh(command).call()
SetBuildStatus(completed, context, success)
} catch (Exception e) {
SetBuildStatus(failed, context, failure)
Error("${exception_message_exec} ${command}").call()
}
}
stage('Upload Helm Chart') {
if (helm_chart_upload == true) {
withCredentials([string(credentialsId: 'nsm-nordix-artifactory-api-key', variable: 'API_KEY')]) {
ExecSh("""
charts=\$(cd _output/helm/ && ls *.tgz)
for chart in \$charts
do
curl -H 'X-JFrog-Art-Api:${API_KEY}' -T _output/helm/\$chart \"https://artifactory.nordix.org/artifactory/cloud-native/meridio/\$chart\"
done
""").call()
}
} else {
Utils.markStageSkippedForConditional('Upload Helm Chart')
}
}
}
)
}
}
// Run the security scan job
def SecurityScan(current_branch, version) {
return {
build job: 'meridio-periodic-security-scan', parameters: [
string(name: 'IMAGE_VERSION', value: "$version"),
string(name: 'CURRENT_BRANCH', value: "$current_branch"),
string(name: 'DRY_RUN', value: env.DRY_RUN)
], wait: true
}
}
// Run the E2e Tests
// Currently skipped
def E2e(current_branch, version) {
return {
build job: 'meridio-e2e-test-kind', parameters: [
string(name: 'MERIDIO_VERSION', value: "$version"),
string(name: 'TAPA_VERSION', value: "$version"),
string(name: 'CURRENT_BRANCH', value: "$current_branch"),
string(name: 'DRY_RUN', value: env.DRY_RUN)
], wait: true
}
}
// Raise error in Jenkins job
def Error(e) {
return {
sh 'git diff'
sh 'git status -s'
Cleanup()
error e
}
}
// Cleanup directory
def Cleanup() {
cleanWs()
}
// Execute command
def ExecSh(command) {
return {
if (env.DRY_RUN != 'true') {
sh """
. \${HOME}/.profile
${command}
"""
} else {
echo "${command}"
}
}
}
// Set the commit status on Github
// https://plugins.jenkins.io/github/#plugin-content-pipeline-examples
def SetBuildStatus(String message, String context, String state) {
if (env.DRY_RUN != 'true') {
step([
$class: 'GitHubCommitStatusSetter',
reposSource: [$class: 'ManuallyEnteredRepositorySource', url: 'https://github.com/Nordix/Meridio'],
commitShaSource: [$class: 'ManuallyEnteredShaSource', sha: GetCommitSha()],
contextSource: [$class: 'ManuallyEnteredCommitContextSource', context: context],
errorHandlers: [[$class: 'ShallowAnyErrorHandler']], // Prevent GitHubCommitStatusSetter to set the job status to unstable
statusResultSource: [ $class: 'ConditionalStatusResultSource', results: [[$class: 'AnyBuildResult', message: message, state: state]] ]
])
}
}
// Return the current commit sha
def GetCommitSha() {
return sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
}
// Returns if any files has been modified/added/removed
def GetModifiedFiles() {
return sh(script: 'git status -s', returnStdout: true).trim()
}