@@ -4,30 +4,211 @@ title: Continuous Integration with Jenkins
44tagline :
55---
66
7- The SD2E project uses [ Jenkins] ( http://jenkins.sd2e.org/ ) for continuous
8- integration (CI). It is now standard practice to set up CI for all Agave apps.
9- This will ensure your deployed app is always up to date with the master branch
10- of your git repo, and it will alert you if jobs are not working.
7+ Jenkins is an automation server for running continuous integration tests. The
8+ SD2E project uses [ Jenkins] ( http://jenkins.sd2e.org/ ) for continuous integration
9+ (CI) to ensure any changes that have been made to apps do not break their core
10+ functionality. It is now standard practice to set up CI for all Agave apps. This
11+ will ensure your deployed app is always up to date with the master branch of
12+ your git repo, and it will alert you if jobs are not working. This guide will
13+ help you integrate CI testing into any app you would like to deploy, but is not
14+ meant to be a replacement for the
15+ [ Jenkins documentation] ( https://jenkins.io/doc/ ) .
1116
1217
1318
1419<br >
15- #### Header 1
20+ #### The Jenkins file
21+
22+ The Jenkins file defines the stages and environment variables of your Jenkins
23+ job. The Jenkins file is written in [ Groovy] ( http://groovy-lang.org/ ) , and
24+ should be located at the top-level directory for your app. For the previous
25+ FastQC app example, the Jenkins file would be in the ` ~/fastqc-app/ ` directory.
26+ Here is an example of a Jenkins file for the FastQC app:
1627
1728```
18- % code block
29+ #!groovy
30+
31+ pipeline {
32+ agent any
33+ environment {
34+ AGAVE_JOB_TIMEOUT = 900
35+ AGAVE_JOB_GET_DIR = "job_output"
36+ AGAVE_DATA_URI = "agave://data-sd2e-community/sample/sailfish/test/read1.fastq"
37+ CONTAINER_REPO = "fastqc"
38+ CONTAINER_TAG = "test"
39+ AGAVE_CACHE_DIR = "${HOME}/credentials_cache/${JOB_BASE_NAME}"
40+ AGAVE_JSON_PARSER = "jq"
41+ AGAVE_TENANTID = "sd2e"
42+ AGAVE_APISERVER = "https://api.sd2e.org"
43+ AGAVE_USERNAME = "sd2etest"
44+ AGAVE_PASSWORD = credentials('sd2etest-tacc-password')
45+ REGISTRY_USERNAME = "sd2etest"
46+ REGISTRY_PASSWORD = credentials('sd2etest-dockerhub-password')
47+ REGISTRY_ORG = credentials('sd2etest-dockerhub-org')
48+ PATH = "${HOME}/bin:${HOME}/sd2e-cloud-cli/bin:${env.PATH}"
49+ }
50+ stages {
51+
52+ stage('Create Oauth client') {
53+ steps {
54+ sh "make-session-client ${JOB_BASE_NAME} ${JOB_BASE_NAME}-${BUILD_ID}"
55+ }
56+ }
57+ stage('Build container') {
58+ steps {
59+ sh "apps-build-container -O ${REGISTRY_USERNAME} --image ${CONTAINER_REPO} --tag ${CONTAINER_TAG}"
60+ }
61+ }
62+ stage('Deploy to TACC.cloud') {
63+ steps {
64+ sh "apps-deploy -T -O ${REGISTRY_USERNAME} --image ${CONTAINER_REPO} --tag ${CONTAINER_TAG}"
65+ sh "cat deploy-*"
66+ }
67+ }
68+ stage('Run a test job') {
69+ steps {
70+ sh "run-test-job deploy-${AGAVE_USERNAME}-job.json ${AGAVE_JOB_TIMEOUT}"
71+ sh "get-test-job-outputs deploy-${AGAVE_USERNAME}-job.json.jobid ${AGAVE_JOB_GET_DIR}"
72+ }
73+ }
74+ stage('Validate results') {
75+ steps {
76+ sh "python -m pytest tests/validate-job --job-directory ${AGAVE_JOB_GET_DIR}"
77+ }
78+ }
79+ }
80+ post {
81+ always {
82+ sh "delete-session-client ${JOB_BASE_NAME} ${JOB_BASE_NAME}-${BUILD_ID}"
83+ }
84+ success {
85+ deleteDir()
86+ }
87+ }
88+ }
89+
1990```
2091
92+ Copy and paste the above text into a file called ` Jenkinsfile ` in the top level
93+ of your ` ~/fastqc-app/ ` directory.
94+
95+ The file is divided into three sections: ` environment ` , ` stages ` , and ` post ` .
96+ The ` environment ` section defines environment variables needed by the Jenkins
97+ server to run the test job. Most of the variables in this section are specific
98+ to the Jenkins server and should be left alone. The ` CONTAINER_REPO ` and
99+ ` CONTAINER_TAG ` variables should, collectively, point to a "test" repository
100+ location so that the versioned app is not overwritten. It is good practice to
101+ use the app name (e.g. "` fastqc ` ") as the ` CONTAINER_REPO ` , and "` test ` " as the
102+ ` CONTAINER_TAG ` .
103+
104+ You may also need to change ` AGAVE_DATA_URI ` if your data is located on some
105+ other system or in a different path. Also note that if your test data is located
106+ in your private storage system, ` data-tacc-work-username ` , you will need to grant
107+ ` READ ` access to the ` sd2etest ` user with the following command:
108+
109+ ```
110+ systems-roles-addupdate -u sd2etest -r USER data-tacc-work-username
111+ ```
112+
113+ The ` stages ` section of the Jenkins file will also remain largely unchanged. You
114+ will typically need the following sections:
115+ 1 . ` Create Oauth client `
116+ 2 . ` Build container `
117+ 3 . ` Deploy to TACC.cloud `
118+ 4 . ` Run a test job ` , and
119+ 5 . ` Validate results `
120+
121+ The first four steps depend on scripts that exist on the Jenkins server, and
122+ should work the same for most apps. The final step must be written by the app
123+ developer, and will be different from app to app. More details on this step are
124+ included in the next section below.
125+
126+ Finally, the ` post ` section uses one more script on the Jenkins server to clean
127+ up the session and exit the Jenkins test. This section should not change.
21128
22129<br >
23- #### Header 2
130+ #### Validating results with pytests
131+
132+ To validate your results, you will need to define pytests that will be run to
133+ verify your app is functional. For the FastQC app, navigate to the tests
134+ directory ` ~/fastqc-app/tests ` , and create a new sub-directory called
135+ ` validate-job ` :
136+ ```
137+ cd ~/fastqc-app/tests
138+ mkdir validate-job
139+ cd validate-job
140+ ```
24141
25- [ Example link] ( https://url/ )
142+ Create two pytests in that directory, ` conftest.py ` and ` test_files.py ` . You can
143+ copy and paste these two examples directly:
26144
145+ ` conftest.py ` :
146+ ```
147+ import pytest
148+
149+ def pytest_addoption(parser):
150+ parser.addoption("--job-directory", action="store", default="job_output",
151+ help="Directory containing output to evaluate")
152+
153+ @pytest.fixture
154+ def job_directory(request):
155+ return request.config.getoption("--job-directory")
156+ ```
157+
158+ ` test_files.py ` :
159+ ```
160+ '''Test for specific files existence in a directory'''
161+ import pytest
162+ import os
163+
164+ '''Parameterize the test with a list of required files'''
165+ @pytest.mark.parametrize("file_list", [
166+ (['reads1_fastqc.html', 'reads1_fastqc.zip'])
167+ ])
168+ def test_files(job_directory,file_list):
169+ '''checks job_directory for existence of all contents of file_list'''
170+ # Existence
171+ listdir = os.listdir(job_directory)
172+ assert(len(list(set(listdir) & set(file_list))) == len(file_list)), \
173+ "Missing files"
174+ # Files are readable and not zero length
175+ for f in file_list:
176+ try:
177+ fstat = os.stat(os.path.join(job_directory, f))
178+ assert (fstat.st_size > 0), "Zero length file: {}".format(f)
179+ except Exception:
180+ raise IOError("Couldn't stat {}".format(f))
181+ ```
182+
183+ The ` conftest.py ` adds a ` --job-directory ` option to pytest, this points to the
184+ location of the output files that are created by running your app. The pytest
185+ ` test_files.py ` checks if files exist and are greater than 0 bytes.
186+
187+ If you are creating pytests for a different app, you can simply change the output
188+ file names in line 7 of ` test_files.py ` , to the file names that are output by your app:
189+
190+ ```
191+ '''Parameterize the test with a list of required files'''
192+ @pytest.mark.parametrize("file_list", [
193+ (['reads1_fastqc.html', 'reads1_fastqc.zip'])
194+ ])
195+ ```
196+ In this example, Jenkins is testing for the existence of ` reads1_fastqc.html `
197+ and ` reads1_fastqc.zip ` , which indicate that the FastQC app ran successfully.
27198
28199<br >
29- #### Header 3
200+ #### Setting up the Jenkins server
201+
202+ Now that you have created a groovy file and defined pytests, you will need to
203+ add your repo to the Jenkins server and define when it should run tests. We
204+ typically have the server run a test every time a changed is pushed to the
205+ master branch, or anytime a merge request to the master branch is made. It may
206+ also be a good idea to schedule weekly or monthly builds to ensure your app
207+ continues working even when no changes have been made to the source repo.
208+
209+ To set up Jenkins tests for your app, follow the instructions in this video tutorial:
30210
211+ <iframe width =" 560 " height =" 315 " src =" https://www.youtube.com/embed/XfhgGZ0CAPw " frameborder =" 0 " allow =" autoplay; encrypted-media " allowfullscreen ></iframe >
31212
32213
33214---
0 commit comments