{"id":41845,"date":"2021-03-03T14:25:12","date_gmt":"2021-03-03T14:25:12","guid":{"rendered":"http:\/\/icloud.pe\/blog\/?guid=aa5c1772745a9414df637866c4a1bc67"},"modified":"2021-03-03T14:25:12","modified_gmt":"2021-03-03T14:25:12","slug":"automate-your-software-builds-with-jenkins","status":"publish","type":"post","link":"https:\/\/icloud.pe\/blog\/automate-your-software-builds-with-jenkins\/","title":{"rendered":"Automate your software builds with Jenkins"},"content":{"rendered":"<p><span class=\"field field-name-field-author field-type-node-reference field-label-hidden\"><br \/>\n      <span class=\"field-item even\"><a href=\"https:\/\/www.cloudpro.co.uk\/authors\/danny-bradbury\">Danny Bradbury<\/a><\/span><br \/>\n  <\/span><\/p>\n<div class=\"field field-name-field-published-date field-type-datetime field-label-hidden\">\n<div class=\"field-items\">\n<div class=\"field-item even\"><span class=\"date-display-single\">3 Mar, 2021<\/span><\/div>\n<\/p><\/div>\n<\/div>\n<p class=\"short-teaser\">\n<a href=\"https:\/\/www.cloudpro.co.uk\/\" title=\"\" class=\"combined-link\"><\/a><\/p>\n<div class=\"field field-name-body\">\n<p><span data-cke-copybin-start=\"1\">\u200b<\/span>Software developers can work well alone, if they&#8217;re in control of all their software assets and tests. Things get trickier, however, when they have to work as part of a team on a fast-moving project with lots of releases. A group of developers can contribute their code to the same source repository, <a href=\"https:\/\/www.itpro.co.uk\/development\/software-development\/355106\/getting-started-with-git\">like Git<\/a>, but they then have to run all the necessary tests to ensure things are working smoothly. Assuming the tests pass, they must build those source files into executable binaries, and then deploy them. That&#8217;s a daunting task that takes a lot of time and organisation on larger software projects.<\/p>\n<p>This is what Jenkins is for. It&#8217;s an open-source tool that co-ordinates those stages into a pipeline. This makes it a useful tool for <a href=\"https:\/\/www.itpro.co.uk\/development\/devops\/358660\/it-pro-panel-defining-devops\">DevOps, a development and deployment approach that automates the various stages of building software<\/a>, creating an efficient conveyor belt system. Teams that get DevOps right with tools like Jenkins can move from version roll-outs every few months to every few days (or even hours), confident that all their tests have been passed.<\/p>\n<p>Jenkins used to be called Hudson, but its development team renamed it after Oracle forked the project and claimed the original name. It&#8217;s free,\u00a0 and runs on operating systems including Windows, Mac, and Linux, and it can also run as <a href=\"https:\/\/www.itpro.co.uk\/development\/containers\/354652\/getting-started-with-docker\">a Docker image.<\/a><\/p>\n<p><a href=\"https:\/\/www.itpro.co.uk\/development\/containers\/354652\/getting-started-with-docker\"><\/a>You can get Jenkins as a downloadable from the<a href=\"https:\/\/www.jenkins.io\/\"> Jenkins.io<\/a> website, but you&#8217;ll need to run the Java runtime environment to support it. Alternatively, you can install it as a Docker container by following the<a href=\"https:\/\/www.jenkins.io\/doc\/book\/installing\/docker\/\"> instructions<\/a> on the official Jenkins site, which is what we&#8217;ll do here. Docker takes a little extra work to set up, but the advantage here is twofold: it solves some dependency problems you might run into with Java, and it also enables you to easily recreate your Jenkins install on any server by copying your Docker file and the Docker run command to run it, which we put into a shell script for increased convenience. Jenkins&#8217; Docker instructions also install a souped-up user interface called Blue Ocean. If you don&#8217;t use the Jenkins Docker instructions you can also install Blue Ocean separately as a plugin.<\/p>\n<div id=\"file-7966\" class=\"file file-image file-image-png contextual-links-region file-content-full-width\">\n<div class=\"content\">    <img decoding=\"async\" src=\"https:\/\/cdn1.cloudpro.co.uk\/sites\/cloudprod7\/files\/styles\/insert_main_wide_image\/public\/2021\/03\/jenkins_-1-start-screen.png?itok=cn0VStRm\" alt=\"\" \/>  <\/div>\n<\/div>\n<p>First, we must create a Python program for Jenkins to work with. We created a simple file called test-myapp.py, stored on our Linux system in <em>\/home\/$USER\/python\/myapp<\/em>. It includes a basic test using the Python PyTest utility:<\/p>\n<table>\n<tbody>\n<tr>\n<td>#test_capitalization<\/p>\n<p>def capitalize_word(word):<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0return word.capitalize()<\/p>\n<p>\u00a0def test_capitalize_word():<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0assert capitalize_word(&#8216;python&#8217;) == &#8216;Python&#8217;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Create a Github repository for it using git init. Commit the file to your repo using <em>git add .<\/em>, and then <em>git commit -m &#8220;first commit&#8221;<\/em>.<\/p>\n<p>Now it&#8217;s time to start Jenkins using the docker run command in the Jenkins teams&#8217; Docker instructions. Once Jenkins is running, you can access it at localhost:8080. It will initially show you a screen with a directory path to a file containing your secure first-time access password. Copy the contents of the file to get logged into the administration screen, and from there it will set up the basic plugins you need to work with the software. Then, you can create a new user account for yourself.<\/p>\n<p>\u00a0<\/p>\n<p>If you&#8217;re not already in the Blue Ocean interface, click on that option in the left sidebar. It will ask you to create a project. Call it <strong><em>myapp<\/em><\/strong> and then select <strong><em>Git<\/em><\/strong> as the project type in the Blue Ocean interface.<\/p>\n<div id=\"file-7965\" class=\"file file-image file-image-jpeg contextual-links-region file-content-full-width\">\n<div class=\"content\">    <img decoding=\"async\" src=\"https:\/\/cdn2.cloudpro.co.uk\/sites\/cloudprod7\/files\/styles\/insert_main_wide_image\/public\/2021\/03\/automate_your_software_builds_with_jenkins-2-add-git.jpg?itok=TKyd19Kp\" alt=\"\" \/>  <\/div>\n<\/div>\n<p>Blue Ocean will now ask you to create a pipeline. Click yes. We&#8217;re going to write this pipeline ourselves in a \u2018Jenkinsfile\u2019, which we&#8217;ll store in our myapp folder.<\/p>\n<p>A Jenkinsfile is a test file describing your pipeline. It contains instructions for the stages of each build. It looks like this:<\/p>\n<table>\n<tbody>\n<tr>\n<td>pipeline {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0agent any<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0stages {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stage(&#8216;Build&#8217;) {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0steps {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0&lt;steps for this stage go here&gt;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stage(&#8216;Test&#8217;) {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0steps {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0&lt;steps for this stage go here&gt;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stage(&#8216;Deploy&#8217;) {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0steps {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0&lt;steps for this stage go here&gt;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>}<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Each stage reflects a step in the build pipeline and we can have as many as we like. Let&#8217;s flesh out this template.<\/p>\n<p>Python programs don&#8217;t need building and deploying in the same way that, say, C++ programs do, because they&#8217;re interpreted. Nevertheless, Jenkins is useful in other ways. We can test our code automatically, and we can also check its formatting to ensure that it&#8217;s easy for other developers to read.<\/p>\n<p>To do this, we need to compile the Python program into bytecode, which is an intermediate stage that happens when you run Python programs. We&#8217;ll call that our build stage. Here&#8217;s the Jenkinsfile for that step:<\/p>\n<table>\n<tbody>\n<tr>\n<td>pipeline {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0agent none<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0stages {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stage(&#8216;Build&#8217;) {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0agent {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0docker {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0image &#8216;python:2-alpine&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0steps {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sh &#8216;python -m py_compile test_myapp.py&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stash(name: &#8216;compiled-results&#8217;, includes: &#8216;*.py*&#8217;)<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>}<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The agent is the external program that runs this stage. We don&#8217;t define a global one, but we do define one for the individual stage. In this case, because we&#8217;re using Docker, it&#8217;s a lightweight Alpine container with a Python implementation.<\/p>\n<p>For our step, we run a shell command that compiles our python file.<\/p>\n<p>Save this in your project folder as \u2018Jenkinsfile\u2019 and then commit it using <em>git add .<\/em> and <em>git commit -m &#8220;add Jenkinsfile&#8221;<\/em>.<\/p>\n<p>Back in the UI, ignore Blue Ocean&#8217;s prompt to create a pipeline. Once it spots the Jenkinsfile in your repo, it&#8217;ll build it from that automatically. Go into the Jenkins dashboard by clicking the exit icon next to the <strong><em>Logout<\/em><\/strong> option on the top right, or clicking the Jenkins name on the top left of the screen, to get to the main Jenkins screen. Look for your new project in the dashboard and on the left, select <strong><em>Scan Multibranch Pipeline Now<\/em><\/strong>.<\/p>\n<p>Wait for a few seconds and Jenkins will scan your Git repo and run the build. Go back into the Blue Ocean interface, and all being well you&#8217;ll see a sunny icon underneath the <strong><em>HEALTH<\/em><\/strong> entry that shows the build succeeded. Click on <strong><em>myapp<\/em><\/strong>, then <strong><em>Branch indexing<\/em><\/strong>, and it&#8217;ll give you a picture of your pipeline and a detailed log.<\/p>\n<div id=\"file-7964\" class=\"file file-image file-image-jpeg contextual-links-region file-content-full-width\">\n<div class=\"content\">    <img decoding=\"async\" src=\"https:\/\/cdn2.cloudpro.co.uk\/sites\/cloudprod7\/files\/styles\/insert_main_wide_image\/public\/2021\/03\/automate_your_software_builds_with_jenkins-3-build-works.jpg?itok=3uEsmXIq\" alt=\"\" \/>  <\/div>\n<\/div>\n<p>Now we will add a test stage. Update your code to look like this:<\/p>\n<table>\n<tbody>\n<tr>\n<td>pipeline {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0agent none<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0stages {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stage(&#8216;Build&#8217;) {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0agent {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0docker {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0image &#8216;python:2-alpine&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0steps {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sh &#8216;python -m py_compile test_myapp.py&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stash(name: &#8216;compiled-results&#8217;, includes: &#8216;*.py*&#8217;)<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stage(&#8216;Test&#8217;) {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0agent {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0docker {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0image &#8216;qnib\/pytest&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0steps {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sh &#8216;py.test &#8211;verbose &#8211;junit-xml test-results\/results.xml test_myapp.py&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0post {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0always {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0junit &#8216;test-results\/results.xml&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>}<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>We&#8217;re using another Docker container to run a simple PyTest (which we included in the code of our myapp.py file). Save this file and update your repo with another <em>git add a .<\/em> and <em>git commit -m &#8220;add test stage to Jenkinsfile&#8221;<\/em>. Then, scan the multibranch pipeline as before. When you drop into Blue Ocean, you&#8217;ll hopefully see success once again. Note that Docker stores everything it runs in its own volume, along with the results. Although you can work some command line magic to access those files directly, you don&#8217;t need to; Jenkins shows you those assets in its UI. Click on the latest stage to open the build details, and find the entry that says <strong><em>py.test \u2013verbose \u2013junit-xml test-results\/results.xml test<\/em><\/strong><strong><em>myapp<\/em><\/strong><strong><em>.py<\/em><\/strong>. Clicking on that shows you the results of your test:<\/p>\n<div id=\"file-7963\" class=\"file file-image file-image-jpeg contextual-links-region file-content-full-width\">\n<div class=\"content\">    <img decoding=\"async\" src=\"https:\/\/cdn1.cloudpro.co.uk\/sites\/cloudprod7\/files\/styles\/insert_main_wide_image\/public\/2021\/03\/automate_your_software_builds_with_jenkins-4-run-test.jpg?itok=hfLMpYiO\" alt=\"\" \/>  <\/div>\n<\/div>\n<p>Everything passed! Now we&#8217;re going to bring it home with the final stage in our demo pipeline: checking the code formatting. There are specific rules for formatting Python code as outlined in the language&#8217;s PEP-8 specifications. We&#8217;ll update our Jenkins file to use a tool called PyLint that will check our code. Here&#8217;s the full Jenkinsfile for all three stages of our pipeline:<\/p>\n<table>\n<tbody>\n<tr>\n<td>pipeline {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0agent none<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0stages {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stage(&#8216;Build&#8217;) {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0agent {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0docker {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0image &#8216;python:2-alpine&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0steps {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sh &#8216;python -m py_compile test_myapp.py&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stash(name: &#8216;compiled-results&#8217;, includes: &#8216;*.py*&#8217;)<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stage(&#8216;Test&#8217;) {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0agent {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0docker {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0image &#8216;qnib\/pytest&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0steps {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sh &#8216;py.test &#8211;verbose &#8211;junit-xml test-results\/results.xml test_myapp.py&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0post {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0always {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0junit &#8216;test-results\/results.xml&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0stage(&#8216;Lint&#8217;) {\u00a0<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0agent {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0docker {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0image &#8216;eeacms\/pylint&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0environment {\u00a0<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0VOLUME = &#8216;$(pwd)\/test_myapp.py&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0IMAGE = &#8216;eeacms\/pylint&#8217;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0steps {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0withEnv([&#8216;PYLINTHOME=.&#8217;]) {<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sh &#8220;pylint ${VOLUME}&#8221;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>}<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Follow the same steps as before: save the file, commit it to your Git repo so that Jenkins sees it, and then rescan the multi-branch pipeline. Then go into Blue Ocean and look at the result. Oh no!<\/p>\n<div id=\"file-7962\" class=\"file file-image file-image-jpeg contextual-links-region file-content-full-width\">\n<div class=\"content\">    <img decoding=\"async\" src=\"https:\/\/cdn1.cloudpro.co.uk\/sites\/cloudprod7\/files\/styles\/insert_main_wide_image\/public\/2021\/03\/automate_your_software_builds_with_jenkins-5-lint-fail.jpg?itok=Gh6oFrYY\" alt=\"\" \/>  <\/div>\n<\/div>\n<p>The pipeline stage failed! That&#8217;s because our code is badly formatted, and PyLint tells us why. We&#8217;ll update our <em>test_myapp.py<\/em> file to make the code compliant:<\/p>\n<table>\n<tbody>\n<tr>\n<td>&#8220;&#8221;&#8221;<\/p>\n<p>Program to capitalize input<\/p>\n<p>&#8220;&#8221;&#8221;<\/p>\n<p>#test_capitalization<\/p>\n<p>def capitalize_word(word):<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0&#8220;&#8221;&#8221; Capitalize a word&#8221;&#8221;&#8221;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0return word.capitalize()<\/p>\n<p>\u00a0def test_capitalize_word():<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0&#8220;&#8221;&#8221;Test to ensure it capitalizes a word correctly&#8221;&#8221;&#8221;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0assert capitalize_word(&#8216;python&#8217;) == &#8216;Python&#8217;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Now, save, commit to your repo, and rescan. Blue Ocean shows that we fixed it (note that in our demo it took us a couple of runs at the Python code to get the formatting right).<\/p>\n<div id=\"file-7961\" class=\"file file-image file-image-jpeg contextual-links-region file-content-full-width\">\n<div class=\"content\">    <img decoding=\"async\" src=\"https:\/\/cdn1.cloudpro.co.uk\/sites\/cloudprod7\/files\/styles\/insert_main_wide_image\/public\/2021\/03\/automate_your_software_builds_with_jenkins-6-last-lint-success.jpg?itok=YdstxJOF\" alt=\"\" \/>  <\/div>\n<\/div>\n<p>You could run all these steps manually yourself, but the beauty of Jenkins is that it automates them all for faster development. That makes the tool invaluable for developers working on a fast cadence as part of a team, but even a single freelance dev, or a hobbyist working on open-source projects, can use this to refine their practice. <\/p>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>      Danny Bradbury<\/p>\n<p>        3 Mar, 2021    <\/p>\n<p>      \u200bSoftware developers can work well alone, if they&#8217;re in control of all their software assets and tests. Things get trickier, however, when they have to work as part of a team on a fast-moving p&#8230;<\/p>\n","protected":false},"author":636,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-41845","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/posts\/41845","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/users\/636"}],"replies":[{"embeddable":true,"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/comments?post=41845"}],"version-history":[{"count":2,"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/posts\/41845\/revisions"}],"predecessor-version":[{"id":41847,"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/posts\/41845\/revisions\/41847"}],"wp:attachment":[{"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/media?parent=41845"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/categories?post=41845"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/icloud.pe\/blog\/wp-json\/wp\/v2\/tags?post=41845"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}