mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 02:37:10 +00:00 
			
		
		
		
	
		
			
	
	
		
			453 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			453 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|   | /* | ||
|  |  * This is a generic jenkinsfile to build Asterisk and optionally | ||
|  |  * perform one or more of the following: | ||
|  |  *  * Publish the API docs to the wiki | ||
|  |  *  * Run the Unit tests | ||
|  |  *  * Run Testsuite Tests | ||
|  |  * | ||
|  |  * This job can be triggered manually from Jenkins or be triggered | ||
|  |  * automatically on a schedule based on a cron string. | ||
|  |  * | ||
|  |  * To use this jenkinsfile, create a new "Multi-Branch Pipeline" job | ||
|  |  * in Jenkins.  For easier configuration, the job name should contain | ||
|  |  * only letters, numbers, or the "-", "_" and "." special characters. | ||
|  |  * Use the "by Jenkinsfile" "Build Configuration" mode and specify | ||
|  |  * the path to this jenkinsfile. | ||
|  |  * | ||
|  |  * When you save this job definition, Jenkins will scan the git | ||
|  |  * repository and find any branches with this Jenkinsfile and then try | ||
|  |  * run the job.  It's expected that the jobs will fail because you | ||
|  |  * haven't create the config file yet. | ||
|  |  * | ||
|  |  * The job is configured from a Jenkins managed config file named | ||
|  |  * "jobConfig".  These files are created using the "Config Files" | ||
|  |  * option of the base job and are unique to a job so you can create | ||
|  |  * multiple jobs based on this Jenkinsfile without conflicts. | ||
|  |  * | ||
|  |  * Create the file as a "Json file" remembering to change the ID | ||
|  |  * from the auto-generated UUID to "jobConfig". | ||
|  |  * | ||
|  |  * Example contents: | ||
|  |  *	{ | ||
|  |  *		cronString: 'H H(0-4) * * *', | ||
|  |  *		jobTimeout: { | ||
|  |  *			timeout: 2, | ||
|  |  *			units: 'HOURS', | ||
|  |  *		}, | ||
|  |  *		jobCleanup: { | ||
|  |  *			keepBuilds: 5, | ||
|  |  *			artifactKeepBuilds: 2 | ||
|  |  *		}, | ||
|  |  *		throttleCategories: [ | ||
|  |  *			'default' | ||
|  |  *		], | ||
|  |  *		docker: [ | ||
|  |  *			images: [ | ||
|  |  *				'asterisk/jenkins-agent-centos7' | ||
|  |  *			] | ||
|  |  *		], | ||
|  |  *		buildAsterisk: [ | ||
|  |  *			build: true, | ||
|  |  *			env: [ | ||
|  |  *				REF_DEBUG: true | ||
|  |  *			] | ||
|  |  *		], | ||
|  |  *		unitTests: [ | ||
|  |  *			run: true, | ||
|  |  *			testCommand: 'test execute all' | ||
|  |  *		] | ||
|  |  *	} | ||
|  |  * | ||
|  |  * NOTE: The JSON file can actually reference variables from the | ||
|  |  * environment using string interpolation.  For example, if you | ||
|  |  * need to substitute the current branch in a value for some reason, | ||
|  |  * you could use: | ||
|  |  *   mybranch: "${BRANCH}" | ||
|  |  */ | ||
|  | 
 | ||
|  | /* | ||
|  |  * All jobConfig parameters have defaults BUT if left that way, | ||
|  |  * only an Asterisk build will be done. | ||
|  |  * | ||
|  |  * NOTE:  Groovy syntax uses brackets "[]" for both arrays and | ||
|  |  * maps/dictionaries where JSON uses brackets "[]" for arrays but | ||
|  |  * braces "{}" for maps/dictionaries.  Your jobConfig file is JSON | ||
|  |  * but the defaults below are Groovy. | ||
|  |  */ | ||
|  | def jobConfig = [ | ||
|  | 	/* Must match a label assigned to agents. */ | ||
|  | 	agentLabel: 'swdev-docker', | ||
|  | 	/* | ||
|  | 	 * https://jenkins.io/doc/book/pipeline/syntax/#cron-syntax | ||
|  | 	 * If empty, job will not be scheduled and must be triggered manually. | ||
|  | 	 */ | ||
|  | 	cronString: '', | ||
|  | 	/* | ||
|  | 	 * An array of strings that name categories defined in Jenkins | ||
|  | 	 * Global Settings under "Throttle Concurrent Builds".  If you | ||
|  | 	 * specify one or more categories, they MUST have been defined | ||
|  | 	 * or the job will fail. | ||
|  | 	 */ | ||
|  | 	throttleCategories: [ | ||
|  | 	], | ||
|  | 	jobTimeout: [ | ||
|  | 		/* How long should the job be allowed to run? */ | ||
|  | 		timeout: 120, | ||
|  | 		/* Common valid units are "MINUTES", "HOURS", "DAYS". */ | ||
|  | 		units: 'MINUTES' | ||
|  | 	], | ||
|  | 	jobCleanup: [ | ||
|  | 		/* The total number of past jobs to keep. */ | ||
|  | 		keepBuilds: 14, | ||
|  | 		/* But only this number will have their artifacts saved. */ | ||
|  | 		artifactKeepBuilds: 7, | ||
|  | 		/* Clean up the workspace on the agent when the job completes. */ | ||
|  | 		cleanupWorkspace: true | ||
|  | 	], | ||
|  | 	docker: [ | ||
|  | 		/* The host and port of our Docker image registry. */ | ||
|  | 		registry: 'swdev-docker0:5000', | ||
|  | 		/* | ||
|  | 		 * An array of images that can be used for this job. | ||
|  | 		 * One will be chosen from the list at random. | ||
|  | 		 */ | ||
|  | 		images: [ | ||
|  | 			'asterisk/jenkins-agent-centos7' | ||
|  | 		], | ||
|  | 	], | ||
|  | 	buildAsterisk: [ | ||
|  | 		/* Build Asterisk */ | ||
|  | 		build: true, | ||
|  | 		/* Additional envuronment variables to pass to buildAsterisk.sh */ | ||
|  | 		env: [ | ||
|  | 		] | ||
|  | 	], | ||
|  | 	unitTests: [ | ||
|  | 		/* Run the Asterisk Unit Tests. */ | ||
|  | 		run: false, | ||
|  | 		/* The Asterisk CLI command to run the tests. */ | ||
|  | 		testCommand: 'test execute all' | ||
|  | 	], | ||
|  | 	wikiDocs: [ | ||
|  | 		/* Build and publish the wiki documentation? */ | ||
|  | 		publish: false, | ||
|  | 		/* The URL to the "publish-docs" repository */ | ||
|  | 		gitURL: "https://gerrit.asterisk.org/publish-docs", | ||
|  | 		/* | ||
|  | 		 * Only for branches that match the regex. | ||
|  | 		 * I.E. Only the base branches excluding master. | ||
|  | 		 */ | ||
|  | 		branchRegex: '^([0-9]+)$' | ||
|  | 	], | ||
|  | 	testsuite: [ | ||
|  | 		/* Run the Testsuite? */ | ||
|  | 		run: false, | ||
|  | 		/* The URL to the "testsuite" repository */ | ||
|  | 		gitURL: "https://gerrit.asterisk.org/testsuite", | ||
|  | 		/* | ||
|  | 		 * The name of the testsuite config file. | ||
|  | 		 * See the "Testsuite" stage below for more info. | ||
|  | 		 */ | ||
|  | 		configFile: 'testsuiteConfig', | ||
|  | 	] | ||
|  | ] | ||
|  | 
 | ||
|  | /* | ||
|  |  * The easiest way to process the above defaults is to merge the | ||
|  |  * values from the jobConfig file over the defaults map.  Groovy | ||
|  |  * provides a standard way to do this but it's not a deep operation | ||
|  |  * so we provide our own deep merge function. | ||
|  |  */ | ||
|  | Map merge(Map onto, Map... overrides) { | ||
|  |     if (!overrides) | ||
|  |         return onto | ||
|  |     else if (overrides.length == 1) { | ||
|  |         overrides[0]?.each { k, v -> | ||
|  |             if (v instanceof Map && onto[k] instanceof Map) | ||
|  |                 merge((Map) onto[k], (Map) v) | ||
|  |             else | ||
|  |                 onto[k] = v | ||
|  |         } | ||
|  |         return onto | ||
|  |     } | ||
|  |     return overrides.inject(onto, { acc, override -> merge(acc, override ?: [:]) }) | ||
|  | } | ||
|  | 
 | ||
|  | /* | ||
|  |  * The job setup steps such as reading the config file and merging the | ||
|  |  * defaults can be done on the "master" node before we send the job off | ||
|  |  * to an agent. | ||
|  |  */ | ||
|  | node('master') { | ||
|  |     def tempJobConfig | ||
|  | 	configFileProvider([configFile(fileId: 'jobConfig', | ||
|  | 		replaceTokens: true, variable: 'JOB_CONFIG_FILE')]) { | ||
|  | 	    echo "Retrieved jobConfig file from ${env.JOB_CONFIG_FILE}" | ||
|  | 	    tempJobConfig = readJSON file: env.JOB_CONFIG_FILE | ||
|  | 	} | ||
|  | 	script { | ||
|  | 	    merge(jobConfig, tempJobConfig) | ||
|  | 	    echo jobConfig.toString() | ||
|  | 	    causeClasses = currentBuild.getBuildCauses() | ||
|  | 	    causeClass = causeClasses[0] | ||
|  | 	    echo "Build Cause: ${causeClass.toString()}" | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | pipeline { | ||
|  | 	triggers { | ||
|  | 		/* If jobConfig.cronString is empty (the default), the trigger will be ignored */ | ||
|  | 		cron jobConfig.cronString | ||
|  | 	} | ||
|  | 
 | ||
|  | 	options { | ||
|  | 		throttle(jobConfig.throttleCategories) | ||
|  | 		timeout(time: jobConfig.jobTimeout.timeout, unit: jobConfig.jobTimeout.units) | ||
|  | 		buildDiscarder( | ||
|  | 		    logRotator(numToKeepStr: "${jobConfig.jobCleanup.keepBuilds}", | ||
|  | 		    artifactNumToKeepStr: "${jobConfig.jobCleanup.artifactKeepBuilds}")) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	agent { | ||
|  | 		label jobConfig.agentLabel | ||
|  | 	} | ||
|  | 
 | ||
|  | 	stages { | ||
|  | 		stage ("Setup") { | ||
|  | 			when { | ||
|  | 				/* | ||
|  | 				 * When you make changes to the base job or a new branch is discovered | ||
|  | 				 * Jenkins tries to run it the job.  We probably don't want this to happen | ||
|  | 				 * so if "BranchIndexing" was teh cause, don't run any of the steps. | ||
|  | 				 */ | ||
|  | 				not { | ||
|  | 					triggeredBy 'BranchIndexingCause' | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			steps { script { | ||
|  | 				createSummary(icon: "/plugin/workflow-job/images/48x48/pipelinejob.png", text: "Docker Host: ${NODE_NAME}") | ||
|  | 				sh "sudo chown -R jenkins:users ." | ||
|  | 				sh "printenv -0 | sort -z | tr '\\0' '\\n'" | ||
|  | 				sh "sudo tests/CI/setupJenkinsEnvironment.sh" | ||
|  | 
 | ||
|  | 				/* Find a docker image, setup parameters and pull image */ | ||
|  | 				def r = currentBuild.startTimeInMillis % jobConfig.docker.images.size() | ||
|  | 				def ri = jobConfig.docker.images[(int)r] | ||
|  | 				echo "Docker Image: ${ri}" | ||
|  | 				def randomImage = jobConfig.docker.registry + "/" + ri | ||
|  | 				echo "Docker Path: ${randomImage}" | ||
|  | 				dockerOptions = "--privileged --ulimit core=0 --ulimit nofile=10240 " + | ||
|  | 					" --tmpfs /tmp:exec,size=1G -v /srv/jenkins:/srv/jenkins:rw -v /srv/cache:/srv/cache:rw " + | ||
|  | 					" --entrypoint=''" | ||
|  | 				buildTag = env.BUILD_TAG.replaceAll(/[^a-zA-Z0-9_.-]/, '-') | ||
|  | 				dockerImage = docker.image(randomImage) | ||
|  | 				dockerImage.pull() | ||
|  | 			}} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		stage ("Build") { | ||
|  | 			when { | ||
|  | 				expression { jobConfig.buildAsterisk.build } | ||
|  | 				not { | ||
|  | 					triggeredBy 'BranchIndexingCause' | ||
|  | 				} | ||
|  | 			} | ||
|  | 			steps { script { | ||
|  | 				dockerImage.inside(dockerOptions + " --name ${buildTag}-build") { | ||
|  | 					echo 'Building..' | ||
|  | 
 | ||
|  | 					withEnv(jobConfig.buildAsterisk.env) { | ||
|  | 						sh "./tests/CI/buildAsterisk.sh --branch-name=${BRANCH_NAME} --output-dir=tests/CI/output/Build --cache-dir=/srv/cache" | ||
|  | 					} | ||
|  | 
 | ||
|  | 					archiveArtifacts allowEmptyArchive: true, defaultExcludes: false, fingerprint: false, | ||
|  | 						artifacts: "tests/CI/output/Build/*" | ||
|  | 				} | ||
|  | 			}} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		stage ("WikiDocs") { | ||
|  | 			when { | ||
|  | 				expression { jobConfig.wikiDocs.publish } | ||
|  | 				not { | ||
|  | 					triggeredBy 'BranchIndexingCause' | ||
|  | 				} | ||
|  | 			} | ||
|  | 			steps { script { | ||
|  | 				dockerImage.inside(dockerOptions + " --name ${buildTag}-wikidocs") { | ||
|  | 					sh "sudo ./tests/CI/installAsterisk.sh --branch-name=${BRANCH_NAME}  --user-group=jenkins:users" | ||
|  | 
 | ||
|  | 					checkout scm: [$class: 'GitSCM', | ||
|  | 						branches: [[name: "master"]], | ||
|  | 							extensions: [ | ||
|  | 								[$class: 'RelativeTargetDirectory', relativeTargetDir: "tests/CI/output/publish-docs"], | ||
|  | 								[$class: 'CloneOption', | ||
|  | 									noTags: true, | ||
|  | 									honorRefspec: true, | ||
|  | 									shallow: false | ||
|  | 								], | ||
|  | 							], | ||
|  | 							userRemoteConfigs: [[url: jobConfig.wikiDocs.gitURL]] | ||
|  | 						] | ||
|  | 					sh "./tests/CI/publishAsteriskDocs.sh --user-group=jenkins:users --branch-name=${BRANCH_NAME} --wiki-doc-branch-regex=\"${jobConfig.wikiDocs.branchRegex}\"" | ||
|  | 				} | ||
|  | 			}} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		stage ("UnitTests") { | ||
|  | 			when { | ||
|  | 				expression { jobConfig.unitTests.run } | ||
|  | 				not { | ||
|  | 					triggeredBy 'BranchIndexingCause' | ||
|  | 				} | ||
|  | 			} | ||
|  | 			steps { script { | ||
|  | 				dockerImage.inside(dockerOptions + " --name ${buildTag}-unittests") { | ||
|  | 					def outputdir = "tests/CI/output/UnitTests" | ||
|  | 					def outputfile = "${outputdir}/unittests-results.xml" | ||
|  | 
 | ||
|  | 					sh "sudo ./tests/CI/installAsterisk.sh --uninstall-all --branch-name=${BRANCH_NAME} --user-group=jenkins:users" | ||
|  | 					sh "tests/CI/runUnittests.sh --user-group=jenkins:users --output-dir='${outputdir}' --output-xml='${outputfile}' --unittest-command='${jobConfig.unitTests.testCommand}'" | ||
|  | 
 | ||
|  | 					archiveArtifacts allowEmptyArchive: true, defaultExcludes: false, fingerprint: true, | ||
|  | 						artifacts: "${outputdir}/**" | ||
|  | 					junit testResults: outputfile, | ||
|  | 						healthScaleFactor: 1.0, | ||
|  | 						keepLongStdio: true | ||
|  | 				} | ||
|  | 			}} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		/* Testsuite Tests | ||
|  | 		 * | ||
|  | 		 * When jobConfig.testsuite.run is true, load the JSON file specified by | ||
|  | 		 * jobConfig.testsuite.configFile (default "testsuiteConfig") and spin off a | ||
|  | 		 * separate docker container for each testGroup contained therein that also | ||
|  | 		 * has its "enabled" property set to true. | ||
|  | 		 * | ||
|  | 		 * If a testGroup has a customTests child, the specified custom tests repo | ||
|  | 		 * will be cloned into "<groupDir>/tests/custom" and can be referenced as | ||
|  | 		 * any other testsuite test. | ||
|  | 		 * | ||
|  | 		 * Example testsuiteConfig file: | ||
|  | 		 * | ||
|  | 		 *	{ | ||
|  | 		 *		testGroups: [ | ||
|  | 		 *			{ | ||
|  | 		 *				name: "ari1-mwi", | ||
|  | 		 *				enabled: false, | ||
|  | 		 *				dir: "tests/CI/output/ari1", | ||
|  | 		 *				runTestsuiteOptions: "--test-timeout=180", | ||
|  | 		 *				testcmd: "--test-regex=tests/rest_api --test-regex=tests/channels/pjsip/.*mwi" | ||
|  | 		 *			}, | ||
|  | 		 *			{ | ||
|  | 		 *				name: "custom1", | ||
|  | 		 *				enabled: false, | ||
|  | 		 * 				dir: "tests/CI/output/custom1", | ||
|  | 		 *				runTestsuiteOptions: "--test-timeout=180", | ||
|  | 		 *				testcmd: "--test-regex=tests/custom/tests/stress", | ||
|  | 		 *				customTests: { | ||
|  | 		 *					branch: "master", | ||
|  | 		 *					gitURL: "http://somehost/private-tests" | ||
|  | 		 *				} | ||
|  | 		 *			} | ||
|  | 		 *		] | ||
|  | 		 *	} | ||
|  | 		 * | ||
|  | 		 */ | ||
|  | 		stage("Testsuite") { | ||
|  | 			when { | ||
|  | 				expression { jobConfig.testsuite.run } | ||
|  | 			} | ||
|  | 			steps {	script { | ||
|  | 				testConfig = [ | ||
|  | 					testGroups: [], | ||
|  | 				] | ||
|  | 				def tempTestConfig | ||
|  | 				configFileProvider([configFile(fileId: jobConfig.testsuite.configFile, variable: 'TESTSUITE_CONFIG_FILE')]) { | ||
|  | 					echo "Retrieved test config file from ${env.TESTSUITE_CONFIG_FILE}" | ||
|  | 					tempTestConfig = readJSON file: env.TESTSUITE_CONFIG_FILE | ||
|  | 				} | ||
|  | 			    merge(testConfig, tempTestConfig) | ||
|  | 
 | ||
|  | 			    tasks = [ : ] | ||
|  | 
 | ||
|  | 			    testConfig.testGroups.each { | ||
|  | 					def testGroup = it | ||
|  | 					tasks[testGroup.name] = { | ||
|  | 						dockerImage.inside("${dockerOptions} --name ${buildTag}-${testGroup.name}") { | ||
|  | 
 | ||
|  | 							lock("${JOB_NAME}.${NODE_NAME}.installer") { | ||
|  | 								sh "sudo ./tests/CI/installAsterisk.sh --uninstall-all --branch-name=${BRANCH_NAME} --user-group=jenkins:users" | ||
|  | 							} | ||
|  | 
 | ||
|  | 							sh "sudo rm -rf ${testGroup.dir} || : " | ||
|  | 
 | ||
|  | 							checkout scm: [$class: 'GitSCM', | ||
|  | 								branches: [[name: "${BRANCH_NAME}"]], | ||
|  | 									extensions: [ | ||
|  | 										[$class: 'RelativeTargetDirectory', relativeTargetDir: testGroup.dir], | ||
|  | 										[$class: 'CloneOption', | ||
|  | 											noTags: true, | ||
|  | 											depth: 100, | ||
|  | 											honorRefspec: true, | ||
|  | 											shallow: true | ||
|  | 										], | ||
|  | 									], | ||
|  | 									userRemoteConfigs: [[url: jobConfig.testsuite.gitURL]] | ||
|  | 								] | ||
|  | 							echo "Test Custom Config: ${testGroup.customTests.toString()}" | ||
|  | 
 | ||
|  | 							if (testGroup.customTests && testGroup.customTests?.branch && testGroup.customTests?.gitURL) { | ||
|  | 								checkout scm: [$class: 'GitSCM', | ||
|  | 									branches: [[name: testGroup.customTests.branch]], | ||
|  | 										extensions: [ | ||
|  | 											[$class: 'RelativeTargetDirectory', relativeTargetDir: "${testGroup.dir}/tests/custom"], | ||
|  | 											[$class: 'CloneOption', | ||
|  | 												noTags: true, | ||
|  | 												depth: 100, | ||
|  | 												honorRefspec: true, | ||
|  | 												shallow: true | ||
|  | 											], | ||
|  | 										], | ||
|  | 										userRemoteConfigs: [[url: testGroup.customTests.gitURL]] | ||
|  | 									] | ||
|  | 							} | ||
|  | 							sh "sudo tests/CI/runTestsuite.sh ${testGroup.runTestsuiteOptions} --testsuite-dir='${testGroup.dir}' --testsuite-command='${testGroup.testcmd}'" | ||
|  | 
 | ||
|  | 							echo "Group result d: ${currentBuild.currentResult}" | ||
|  | 
 | ||
|  | 							archiveArtifacts allowEmptyArchive: true, defaultExcludes: false, fingerprint: true, | ||
|  | 								artifacts: "${testGroup.dir}/asterisk-test-suite-report.xml, ${testGroup.dir}/logs/**, ${testGroup.dir}/core*.txt" | ||
|  | 
 | ||
|  | 							junit testResults: "${testGroup.dir}/asterisk-test-suite-report.xml", | ||
|  | 								healthScaleFactor: 1.0, | ||
|  | 								keepLongStdio: true | ||
|  | 						} | ||
|  | 					} | ||
|  | 			    } | ||
|  | 			    parallel tasks | ||
|  | 			}} | ||
|  | 		} | ||
|  | 	} | ||
|  | 	post { | ||
|  | 		cleanup { | ||
|  | 			script { | ||
|  | 				if (jobConfig.jobCleanup.cleanupWorkspace) { | ||
|  | 					cleanWs deleteDirs: true, notFailBuild: false | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 		success { | ||
|  | 			echo "Reporting ${currentBuild.currentResult} Passed" | ||
|  | 		} | ||
|  | 		failure { | ||
|  | 			echo "Reporting ${currentBuild.currentResult}: Failed: Fatal Error" | ||
|  | 		} | ||
|  | 		unstable { | ||
|  | 			echo "Reporting ${currentBuild.currentResult}: Failed: Tests Failed" | ||
|  | 		} | ||
|  | 	} | ||
|  | } |