Skip to content

Commit 83d8e3e

Browse files
Merge pull request #15 from ambitus/jenkins-ci-cd
Jenkins ci cd
2 parents 32df754 + 6dba305 commit 83d8e3e

File tree

5 files changed

+323
-55
lines changed

5 files changed

+323
-55
lines changed

.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
ignore = W503,E122,E203
33
max-line-length = 99
44
per-file-ignores = __init__.py:F401
5+
exclude = venv_*

Jenkinsfile

Lines changed: 286 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,314 @@
1+
def python_versions
2+
def python_executables_and_wheels_map
3+
14
pipeline {
25
agent {
36
node {
47
label 'zOS_pyRACF'
58
}
69
}
710

11+
parameters {
12+
// Note: Each Python version listed must be installed on the
13+
// build agent and must be added to '$PATH' and '$LIBPATH'.
14+
string (
15+
name: "pythonVersions",
16+
defaultValue: "",
17+
description: "(Required Always) Comma separated list of Python versions to build wheels for (i.e., Use '10,11' for Python 3.10 and Python 3.11)."
18+
)
19+
booleanParam(
20+
name: "createRelease",
21+
defaultValue: false,
22+
description: "Toggle whether or not to create a release from this revision."
23+
)
24+
string(
25+
name: "releaseTag",
26+
defaultValue: "",
27+
description: "(Required When Creating Releases) This will be the git tag and version number of the release."
28+
)
29+
string(
30+
name: "gitHubMilestoneLink",
31+
defaultValue: "",
32+
description: "(Required When Creating Releases) This is the GitHub Milestore URL that coresponds to the release."
33+
)
34+
booleanParam(
35+
name: "preRelease",
36+
defaultValue: true,
37+
description: "Toggle whether or not this is a pre-release."
38+
)
39+
}
40+
841
options {
942
ansiColor('css')
1043
}
1144

1245
stages {
13-
stage('Install Dependencies') {
46+
stage('Parameter Validation') {
1447
steps {
15-
// Uninstall pyRACF to clean environment.
16-
// Install wheel to build and publish pyRACF as a python wheel.
17-
sh """
18-
python3 --version
19-
python3 -m pip uninstall pyracf -y
20-
python3 -m pip install -r requirements.txt
21-
python3 -m pip install -r requirements-development.txt
22-
python3 -m pip install wheel>=0.41.0
23-
"""
48+
script {
49+
if (params.pythonVersions == "") {
50+
error("'pythonVersions' is required parameter.")
51+
}
52+
if (params.createRelease) {
53+
if (params.releaseTag == "") {
54+
error("'releaseTag' is a required parameter when creating a release.")
55+
}
56+
if (params.gitHubMilestoneLink == "") {
57+
error("'gitHubMilestoneLink' is a required parameter when creating a release.")
58+
}
59+
}
60+
}
2461
}
2562
}
26-
stage('Unit Test') {
63+
stage('Build Python Executables & Wheels Map') {
2764
steps {
28-
sh """
29-
flake8 .
30-
pylint --recursive=y .
31-
coverage run tests/test_runner.py
32-
coverage report -m
33-
"""
65+
script {
66+
python_versions = params.pythonVersions.split(",")
67+
python_executables_and_wheels_map = (
68+
create_python_executables_and_wheels_map(python_versions)
69+
)
70+
}
71+
}
72+
}
73+
stage('Build Virtual Environments') {
74+
steps {
75+
script {
76+
for (python in python_executables_and_wheels_map.keySet()) {
77+
build_virtual_environment(python)
78+
}
79+
}
80+
}
81+
}
82+
stage('Lint & Unit Test') {
83+
steps {
84+
script {
85+
for (python in python_executables_and_wheels_map.keySet()) {
86+
lint_and_unit_test(python)
87+
}
88+
}
3489
}
3590
}
3691
stage('Function Test') {
3792
steps {
38-
sh """
39-
python3 setup.py install --user
40-
cd tests/function_test
41-
python3 function_test.py
42-
"""
93+
script {
94+
for (python in python_executables_and_wheels_map.keySet()) {
95+
function_test(
96+
python,
97+
python_executables_and_wheels_map[python]["defaultName"]
98+
)
99+
}
100+
}
43101
}
44102
}
45103
stage('Publish') {
46-
when { tag "*" }
104+
when {
105+
expression { params.createRelease == true }
106+
}
47107
steps {
48-
sh "python3 setup.py bdist_wheel upload -r test"
108+
publish(
109+
python_executables_and_wheels_map,
110+
params.releaseTag,
111+
env.BRANCH_NAME,
112+
params.gitHubMilestoneLink,
113+
params.preRelease
114+
)
49115
}
50116
}
51117
}
118+
post {
119+
always {
120+
echo "Cleaning up workspace..."
121+
cleanWs()
122+
}
123+
}
124+
}
125+
126+
def create_python_executables_and_wheels_map(python_versions) {
127+
def os = sh(
128+
returnStdout: true,
129+
script: "uname"
130+
).trim().replace("/", "").toLowerCase()
131+
def zos_release = sh(
132+
returnStdout: true,
133+
script: "uname -r"
134+
).trim().replace(".", "_")
135+
def processor = sh(
136+
returnStdout: true,
137+
script: "uname -m"
138+
).trim()
139+
def pyracf_version = sh(
140+
returnStdout: true,
141+
script: "cat pyproject.toml | grep version | cut -d'=' -f2 | cut -d'\"' -f2"
142+
).trim()
143+
144+
python_executables_and_wheels_map = [:]
145+
146+
for (version in python_versions) {
147+
python_executables_and_wheels_map["python3.${version}"] = [
148+
"defaultName": (
149+
"pyRACF-${pyracf_version}-cp3${version}-cp3${version}-${os}_${zos_release}_${processor}.whl"
150+
),
151+
"publishName": "pyRACF-${pyracf_version}-cp3${version}-none-any.whl"
152+
]
153+
}
154+
155+
return python_executables_and_wheels_map
156+
}
157+
158+
def build_virtual_environment(python) {
159+
echo "Building virtual environment for '${python}'..."
160+
161+
sh """
162+
${python} --version
163+
rm -rf venv_${python}
164+
${python} -m venv venv_${python}
165+
. venv_${python}/bin/activate
166+
${python} -m pip install -r requirements.txt
167+
${python} -m pip install -r requirements-development.txt
168+
"""
169+
}
170+
171+
def lint_and_unit_test(python) {
172+
echo "Running linters and unit tests for '${python}'..."
173+
174+
sh """
175+
. venv_${python}/bin/activate
176+
${python} -m flake8 .
177+
${python} -m pylint --recursive=y .
178+
cd tests
179+
${python} -m coverage run test_runner.py
180+
${python} -m coverage report -m
181+
"""
182+
}
183+
184+
def function_test(python, wheel) {
185+
echo "Running function test for '${python}'..."
186+
187+
sh """
188+
git clean -f -d -e 'venv_*'
189+
. venv_${python}/bin/activate
190+
${python} -m pip wheel .
191+
${python} -m pip install ${wheel}
192+
cd tests/function_test
193+
${python} function_test.py
194+
"""
195+
}
196+
197+
def publish(
198+
python_executables_and_wheels_map,
199+
release,
200+
git_branch,
201+
milestone,
202+
pre_release
203+
) {
204+
if (pre_release == true) {
205+
pre_release = "true"
206+
}
207+
else {
208+
pre_release = "false"
209+
}
210+
withCredentials(
211+
[
212+
string(
213+
credentialsId: 'pyracf-github-access-token',
214+
variable: 'github_access_token'
215+
),
216+
string(
217+
credentialsId: 'pyracf-pypi-username',
218+
variable: 'pypi_username'
219+
),
220+
string(
221+
credentialsId: 'pyracf-pypi-password',
222+
variable: 'pypi_password'
223+
)
224+
]
225+
) {
226+
227+
// Creating GitHub releases:
228+
// https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release
229+
// Uploading release assets:
230+
// https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset
231+
232+
// Use single quotes for credentials since it is the most secure
233+
// method for interpolating secrets according to the Jenkins docs:
234+
// https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#string-interpolation
235+
236+
echo "Creating '${release}' GitHub release..."
237+
238+
def description = build_description(python_executables_and_wheels_map, release, milestone)
239+
240+
def release_id = sh(
241+
returnStdout: true,
242+
script: (
243+
'curl -f -v -L '
244+
+ '-X POST '
245+
+ '-H "Accept: application/vnd.github+json" '
246+
+ '-H "Authorization: Bearer ${github_access_token}" '
247+
+ '-H "X-GitHub-Api-Version: 2022-11-28" '
248+
+ "https://api.github.com/repos/ambitus/pyracf/releases "
249+
+ "-d '{"
250+
+ " \"tag_name\": \"${release}\","
251+
+ " \"target_commitish\": \"${git_branch}\","
252+
+ " \"name\": \"${release}\","
253+
+ " \"body\": \"${description}\","
254+
+ " \"draft\": false,"
255+
+ " \"prerelease\": ${pre_release},"
256+
+ " \"generate_release_notes\":false"
257+
+ "}' | grep '\"id\": ' | head -n1 | cut -d':' -f2 | cut -d',' -f1"
258+
)
259+
).trim()
260+
261+
for (python in python_executables_and_wheels_map.keySet()) {
262+
def wheel_default = python_executables_and_wheels_map[python]["defaultName"]
263+
def wheel_publish = python_executables_and_wheels_map[python]["publishName"]
264+
265+
echo "Cleaning repo and building '${wheel_default}'..."
266+
267+
sh """
268+
git clean -f -d -e 'venv_*'
269+
. venv_${python}/bin/activate
270+
${python} -m pip wheel .
271+
"""
272+
273+
echo "Uploading '${wheel_default}' as '${wheel_publish}' to '${release}' GitHub release..."
274+
275+
sh(
276+
'curl -f -v -L '
277+
+ '-X POST '
278+
+ '-H "Accept: application/vnd.github+json" '
279+
+ '-H "Authorization: Bearer ${github_access_token}" '
280+
+ '-H "X-GitHub-Api-Version: 2022-11-28" '
281+
+ '-H "Content-Type: application/octet-stream" '
282+
+ "\"https://uploads.github.com/repos/ambitus/pyracf/releases/${release_id}/assets?name=${wheel_publish}\" "
283+
+ "--data-binary \"@${wheel_default}\""
284+
)
285+
286+
echo "Uploading '${wheel_default}' as '${wheel_publish}' to pypi repository..."
287+
288+
sh(
289+
". venv_${python}/bin/activate && "
290+
+ "mv ${wheel_default} ${wheel_publish} && "
291+
+ "${python} -m twine upload --repository test ${wheel_publish} "
292+
+ '--non-interactive '
293+
+ '-u ${pypi_username} '
294+
+ '-p ${pypi_password}'
295+
)
296+
}
297+
}
298+
}
299+
300+
def build_description(python_executables_and_wheels_map, release, milestone) {
301+
def description = "Release Milestone: ${milestone}\\n \\n \\n"
302+
303+
for (python in python_executables_and_wheels_map.keySet()) {
304+
def wheel = python_executables_and_wheels_map[python]["publishName"]
305+
python = python.replace("python", "Python ")
306+
description += (
307+
"Install for ${python}:\\n"
308+
+ "```\\ncurl -O -L https://github.com/ambitus/pyracf/releases/download/${release}/${wheel} "
309+
+ "&& python3 -m pip install ${wheel}\\n```\\n"
310+
)
311+
}
312+
313+
return description
52314
}

0 commit comments

Comments
 (0)