{"id":2568,"date":"2021-02-18T17:10:08","date_gmt":"2021-02-18T22:10:08","guid":{"rendered":"https:\/\/blogs.mathworks.com\/developer\/?p=2568"},"modified":"2021-02-18T17:30:19","modified_gmt":"2021-02-18T22:30:19","slug":"configuration-as-code","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/developer\/2021\/02\/18\/configuration-as-code\/","title":{"rendered":"An Ode to Configuration as Code"},"content":{"rendered":"\r\n\r\n<div class=\"content\"><!--introduction--><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/developer\/files\/y2021-Dark-Stormy.jpg\" alt=\"\"> <\/p><!--\/introduction--><p>It was a dark and stormy night. The dedicated engineer careened across the rain and mud to deliver her critical package through the challenging terrain of the notorious <b><i>Pipeline Traverse<\/i><\/b>. She had a delivery of the utmost importance and time was of the essence. However, her confidence was strong. After all, she had delivered similar packages hundreds of times. She quickly but carefully ushered this important delivery, navigating around all of the familiar potholes, hairpin turns, and imposing cliff faces. She was just beginning to really enjoy the thrill of the efficiency and precision of her path when the disaster struck.<\/p><p>You see, the familiar path of the <b><i>Pipeline Traverse<\/i><\/b> changed! It was no longer the same route that she was expecting, and unfortunately she was never notified of the change. The crash was big. And costly. But worst of all, she now found herself lost in the middle of an unfamiliar path with no idea how she could make her delivery. She was lost, and the package could not be delivered as needed.<\/p><p>Let me reign myself in before I get too engrossed in this story and quit my day job to pursue cheeseball novels. Cheese aside, isn't our protagonist in this story relatable to anyone involved in managing a software delivery pipeline? It seems just when we are most confident in our delivery pipeline that disaster strikes (usually at 4:30 on a Friday afternoon <i>amirite<\/i> ?).<\/p><p>And if the problem is in your pipeline or your pipeline configuration, when it strikes it can be really challenging to debug. This is especially true if your CI\/CD pipeline is buried in the UI. Have you ever found yourself muttering things like:<\/p><p><i>\"Where did I set this pipeline up?\"<\/i><\/p><p><i>\"Who changed my pipeline?!\"<\/i><\/p><p><i>\"I wish I didn't need this boiler plate for all my projects!\"<\/i><\/p><p>...or, if you ever have the unpleasant experience of data loss or corruption of your CI config data...<\/p><p><i>\"How in the world did I set that up again (over the course of 3 years  and multiple changes)?\"<\/i><\/p><p>Well these sentiments are what invariably lead to a better way. Sooner or later most projects address the problem of pipeline management through the principle of <i>Configuration as Code<\/i>. This is one of the many blog topics I have promised you we would address and discuss here, and so finally here we are.<\/p><p>Configuration as Code follows the principle that we minimize CI configuration in a CI server UI. What we do in the pipeline should not be defined as obscure and opaque metadata that is held onto by a CI server instance somewhere (where? who knows!). We should instead define that process and pipeline with the code - where the changes happen. Where the changes are recorded, can be reverted, and are completely independent of a particular CI server instance and it's state.<\/p><p>With our protagonist delivering along the <b><i>Pipeline Traverse<\/i><\/b>, while she may not have been able to avoid all crashes, when a problem does show up it is much easier to diagnose and pinpoint the cause and ultimately the way out of the canyon.<\/p><p>Our <a href=\"https:\/\/plugins.jenkins.io\/matlab\/\">Jenkins plugin<\/a> supports this. Also most other platforms we support, like <a href=\"https:\/\/circleci.com\/developer\/orbs\/orb\/mathworks\/matlab\">CircleCI<\/a>, <a href=\"https:\/\/docs.travis-ci.com\/user\/languages\/matlab\/\">Travis CI<\/a>, <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=MathWorks.matlab-azure-devops-extension\">Azure DevOps<\/a>, and <a href=\"https:\/\/github.com\/matlab-actions\/overview\">GitHub Actions<\/a> follow this principle as well.<\/p><p>To see this work for our Jenkins plugin, let me show you how you can translate the project we discussed <a href=\"https:\/\/blogs.mathworks.com\/developer\/2020\/10\/07\/pluggin-it-in-with-jenkins\/\">here<\/a> over to a Jenkinsfile using the plugin's support for a feature called Jenkins Pipelines. This project includes a MEX compile step and a subsequent step to run the tests and produce test result and coverage artifacts.<\/p><p>It's actually quite easy. This project had the following steps in its pipeline:<\/p><div><ol><li>Tell Jenkins where MATLAB is (there's actually <a href=\"https:\/\/github.com\/jenkinsci\/matlab-plugin\/releases\/tag\/matlab-2.4.0\">more news<\/a> which makes this easier to manage as well, but maybe that's a future blog post)<\/li><li>Compile the mex files<\/li><li>Run the tests<\/li><li>Process the test result and coverage artifacts<\/li><\/ol><\/div><p>Now instead of defining these in the UI, we are going to create a <tt>Jenkinsfile<\/tt> that contains these steps of the pipeline, and this we can check into our project right alongside our code.<\/p><p>Let's start with a basic pipeline that specifies it can use any available Jenkins agent and has a placeholder to add our stages to the pipeline.<\/p><pre class=\"language-matlab\">pipeline <span class=\"string\">{<\/span>\r\n    agent <span class=\"string\">any<\/span>\r\n    stages <span class=\"string\">{<\/span>\r\n    }\r\n}\r\n<\/pre><p><b>Step 1<\/b><\/p><p>Remember our pipeline had 4 steps, so let's start with step 1. We need to tell Jenkins where MATLAB is. Actually this is really just done by setting the environment such that the machine itself knows where MATLAB is on the system <tt>PATH<\/tt>. When invoking MATLAB the plugin will simply call MATLAB from the shell, so enabling this to work correctly simply means prepending the <tt>PATH<\/tt> environment variable with MATLAB for the duration of this pipeline so that invoking <tt>matlab<\/tt> from the shell will invoke the desired MATLAB set up on the build agent. This looks like so:<\/p><pre class=\"language-matlab\">environment <span class=\"string\">{<\/span>\r\n    PATH = <span class=\"string\">\"\/Applications\/MATLAB_R2020b.app\/bin:${PATH}\"<\/span>\r\n}\r\n<\/pre><p><b>Step 2<\/b><\/p><p>The next step is to build the mex files. As a reminder the example project we are working with here, <a href=\"https:\/\/github.com\/libDirectional\/libDirectional\">libDirectional<\/a>, has a nice function called <tt>compileAll<\/tt> that does the trick for us. Remember, we showed <a href=\"https:\/\/blogs.mathworks.com\/developer\/2020\/08\/19\/tests-in-projects\/\">here<\/a> that putting this in the form of a MATLAB project helps get our environment setup correctly. So our command is just a quick opening of the project and a call to the nifty <tt>compileAll<\/tt> function. Here we use the <tt>runMATLABCommand<\/tt> step that comes as part of the plugin. We don't need to worry about how MATLAB launches (which varies with different releases), we just worry about the command we need to invoke. It's as easy as adding a stage with this step inside of it:<\/p><pre class=\"language-matlab\">stage(<span class=\"string\">'Compile MEX files'<\/span>) {\r\n    steps <span class=\"string\">{<\/span>\r\n        runMATLABCommand <span class=\"string\">'openProject(pwd); compileAll'<\/span>\r\n    }\r\n}\r\n<\/pre><p><b>Step 3<\/b><\/p><p>Now let's run the tests! Last time we touched on this we ran all the tests defined in the project and we generated test results in the TAP format and code coverage in the Cobertura format. I am going to make a slight tweak here, because the TAPPlugin, which processes these TAP results, doesn't support Jenkins pipelines as well as the JUnit plugin. As you might be starting to see, this can be done with some straightforward pipeline syntax that matches the same kind of thing we can do through the UI.<\/p><pre class=\"language-matlab\">stage(<span class=\"string\">'Run MATLAB tests'<\/span>) {\r\n    steps <span class=\"string\">{<\/span>\r\n        runMATLABTests(\r\n            testResultsJUnit: <span class=\"string\">'matlabTestArtifacts\/junittestreport.xml'<\/span>,\r\n            codeCoverageCobertura: <span class=\"string\">'matlabTestArtifacts\/cobertura.xml'<\/span>\r\n        )\r\n    }\r\n}\r\n<\/pre><p><b>Step 4<\/b><\/p><p>...and finally, now that we have built our project, tested it and generated some test results and coverage artifacts, we can now process those artifacts using other plugins meant for that purpose (the <a href=\"https:\/\/plugins.jenkins.io\/junit\/\">JUnit plugin<\/a> and the <a href=\"https:\/\/plugins.jenkins.io\/code-coverage-api\/\">Code Coverage API plugin<\/a>). I'll just add them to the same stage as the test run.<\/p><pre class=\"language-matlab\">stage(<span class=\"string\">'Run MATLAB tests'<\/span>) {\r\n    steps <span class=\"string\">{<\/span>\r\n        runMATLABTests(\r\n            testResultsJUnit: <span class=\"string\">'matlabTestArtifacts\/junittestreport.xml'<\/span>,\r\n            codeCoverageCobertura: <span class=\"string\">'matlabTestArtifacts\/cobertura.xml'<\/span>\r\n        )\r\n        junit <span class=\"string\">'matlabTestArtifacts\/junittestreport.xml'<\/span>\r\n        publishCoverage <span class=\"string\">adapters:<\/span> <span class=\"string\">[coberturaAdapter('matlabTestArtifacts\/cobertura.xml')]<\/span>\r\n    }\r\n}\r\n<\/pre><p>That's it, that's all we need. Here is the whole Jenkinsfile for reference:<\/p><pre class=\"language-matlab\">pipeline <span class=\"string\">{<\/span>\r\n    agent <span class=\"string\">any<\/span>\r\n    environment <span class=\"string\">{<\/span>\r\n        PATH = <span class=\"string\">\"\/Applications\/MATLAB_R2020b.app\/bin:${PATH}\"<\/span>\r\n    }\r\n    stages <span class=\"string\">{<\/span>\r\n        stage(<span class=\"string\">'Compile MEX files'<\/span>) {\r\n            steps <span class=\"string\">{<\/span>\r\n                runMATLABCommand <span class=\"string\">'openProject(pwd); compileAll'<\/span>\r\n            }\r\n        }\r\n        stage(<span class=\"string\">'Run MATLAB tests'<\/span>) {\r\n            steps <span class=\"string\">{<\/span>\r\n                runMATLABTests(\r\n                    testResultsJUnit: <span class=\"string\">'matlabTestArtifacts\/junittestreport.xml'<\/span>,\r\n                    codeCoverageCobertura: <span class=\"string\">'matlabTestArtifacts\/cobertura.xml'<\/span>\r\n                    )\r\n                junit <span class=\"string\">'matlabTestArtifacts\/junittestreport.xml'<\/span>\r\n                publishCoverage <span class=\"string\">adapters:<\/span> <span class=\"string\">[coberturaAdapter('matlabTestArtifacts\/cobertura.xml')]<\/span>\r\n            }\r\n        }\r\n    }\r\n}\r\n<\/pre><p>Now just check something like that into the root of your repository, and you will be good to go. Here's how you create a Jenkins job that uses it:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/developer\/files\/y2021NewPipelineProject.gif\" alt=\"\"> <\/p><p>...and now you can see the pipeline runs just fine and dandy as it pulls its pipeline instructions from the Jenkinsfile instead of the Jenkins UI:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/developer\/files\/y2021RunPipelineProject.gif\" alt=\"\"> <\/p><p>...and now since this pipeline configuration is checked in, it travels with the code, it can be reverted with the code, it can be reviewed with the code, and it can be reasoned with the code. Code is beautiful, especially when backed by a little bit of source control, MATLAB projects, and what we hope are useful CI platform integrations.<\/p><p>Now let's all burst out in song!!<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_b0e1e9978d51451bba2b7f8bc96f48f6() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='b0e1e9978d51451bba2b7f8bc96f48f6 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' b0e1e9978d51451bba2b7f8bc96f48f6';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2021 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_b0e1e9978d51451bba2b7f8bc96f48f6()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2020b<br><\/p><\/div><!--\r\nb0e1e9978d51451bba2b7f8bc96f48f6 ##### SOURCE BEGIN #####\r\n%% An Ode to Configuration as Code\r\n%\r\n% <<y2021-Dark-Stormy.jpg>>\r\n%\r\n%% \r\n% It was a dark and stormy night. The dedicated engineer careened across\r\n% the rain and mud to deliver her critical package through the challenging\r\n% terrain of the notorious *_Pipeline Traverse_*. She had a delivery of the\r\n% utmost importance and time was of the essence. However, her confidence\r\n% was strong. After all, she had delivered similar packages hundreds of\r\n% times. She quickly but carefully ushered this important delivery,\r\n% navigating around all of the familiar potholes, hairpin turns, and\r\n% imposing cliff faces. She was just beginning to really enjoy the thrill\r\n% of the efficiency and precision of her path when the disaster struck.\r\n%\r\n% You see, the familiar path of the *_Pipeline Traverse_* changed! It was\r\n% no longer the same route that she was expecting, and unfortunately she\r\n% was never notified of the change. The crash was big. And costly. But\r\n% worst of all, she now found herself lost in the middle of an unfamiliar\r\n% path with no idea how she could make her delivery. She was lost, and the\r\n% package could not be delivered as needed.\r\n%\r\n% Let me reign myself in before I get too engrossed in this story and quit\r\n% my day job to pursue cheeseball novels. Cheese aside, isn't our\r\n% protagonist in this story relatable to anyone involved in\r\n% managing a software delivery pipeline? It seems just when we are most\r\n% confident in our delivery pipeline that disaster strikes (usually at 4:30\r\n% on a Friday afternoon _amirite_ ?).\r\n%\r\n% And if the problem is in your pipeline or your pipeline configuration,\r\n% when it strikes it can be really challenging to debug. This is especially\r\n% true if your CI\/CD pipeline is buried in the UI. Have you ever found\r\n% yourself muttering things like:\r\n%\r\n% _\"Where did I set this pipeline up?\"_\r\n%\r\n% _\"Who changed my pipeline?!\"_\r\n%\r\n% _\"I wish I didn't need this boiler plate for all my projects!\"_\r\n%\r\n% ...or, if you ever have the unpleasant experience of data loss or\r\n% corruption of your CI config data...\r\n%\r\n% _\"How in the world did I set that up again (over the course of 3 years\r\n%  and multiple changes)?\"_\r\n%\r\n% Well these sentiments are what invariably lead to a better way. Sooner or\r\n% later most projects address the problem of pipeline management through\r\n% the principle of _Configuration as Code_. This is one of the many blog\r\n% topics I have promised you we would address and discuss here, and so\r\n% finally here we are.\r\n%\r\n% Configuration as Code follows the principle that we minimize CI\r\n% configuration in a CI server UI. What we do in the pipeline should not be\r\n% defined as obscure and opaque metadata that is held onto by a CI server\r\n% instance somewhere (where? who knows!). We should instead define that\r\n% process and pipeline with the code - where the changes happen. Where the\r\n% changes are recorded, can be reverted, and are completely independent of\r\n% a particular CI server instance and it's state.\r\n%\r\n% With our protagonist delivering along the *_Pipeline Traverse_*, while\r\n% she may not have been able to avoid all crashes, when a problem does show\r\n% up it is much easier to diagnose and pinpoint the cause and ultimately\r\n% the way out of the canyon.\r\n%\r\n% Our <https:\/\/plugins.jenkins.io\/matlab\/ Jenkins plugin> supports this.\r\n% Also most other platforms we support, like\r\n% <https:\/\/circleci.com\/developer\/orbs\/orb\/mathworks\/matlab CircleCI>,\r\n% <https:\/\/docs.travis-ci.com\/user\/languages\/matlab\/ Travis CI>,\r\n% <https:\/\/marketplace.visualstudio.com\/items?itemName=MathWorks.matlab-azure-devops-extension\r\n% Azure DevOps>, and <https:\/\/github.com\/matlab-actions\/overview  GitHub\r\n% Actions> follow this principle as well.\r\n%\r\n% To see this work for our Jenkins plugin, let me show you how you can\r\n% translate the project we discussed\r\n% <https:\/\/blogs.mathworks.com\/developer\/2020\/10\/07\/pluggin-it-in-with-jenkins\/\r\n% here> over to a Jenkinsfile using the plugin's support for a feature\r\n% called Jenkins Pipelines. This project includes a MEX compile step and a\r\n% subsequent step to run the tests and produce test result and coverage\r\n% artifacts.\r\n%\r\n% It's actually quite easy. This project had the following steps in its\r\n% pipeline:\r\n%\r\n% # Tell Jenkins where MATLAB is (there's actually\r\n% <https:\/\/github.com\/jenkinsci\/matlab-plugin\/releases\/tag\/matlab-2.4.0\r\n% more news> which makes this easier to manage as well, but maybe that's a future blog post)\r\n% # Compile the mex files\r\n% # Run the tests\r\n% # Process the test result and coverage artifacts\r\n% \r\n% Now instead of defining these in the UI, we are going to create a\r\n% |Jenkinsfile| that contains these steps of the pipeline, and this we can\r\n% check into our project right alongside our code.\r\n%\r\n% Let's start with a basic pipeline that specifies it can use any available\r\n% Jenkins agent and has a placeholder to add our stages to the pipeline.\r\n%\r\n% \r\n%   pipeline {\r\n%       agent any \r\n%       stages {\r\n%       }\r\n%   }\r\n% \r\n% *Step 1*\r\n%\r\n% Remember our pipeline had 4 steps, so let's start with step 1. We need to\r\n% tell Jenkins where MATLAB is. Actually this is really just done by\r\n% setting the environment such that the machine itself knows where MATLAB\r\n% is on the system |PATH|. When invoking MATLAB the plugin will simply call\r\n% MATLAB from the shell, so enabling this to work correctly simply means\r\n% prepending the |PATH| environment variable with MATLAB for the duration\r\n% of this pipeline so that invoking |matlab| from the shell will invoke the\r\n% desired MATLAB set up on the build agent. This looks like so:\r\n%\r\n%   environment {\r\n%       PATH = \"\/Applications\/MATLAB_R2020b.app\/bin:${PATH}\"\r\n%   }\r\n% \r\n% *Step 2*\r\n%\r\n% The next step is to build the mex files. As a reminder the example\r\n% project we are working with here,\r\n% <https:\/\/github.com\/libDirectional\/libDirectional libDirectional>, has a\r\n% nice function called |compileAll| that does the trick for us. Remember,\r\n% we showed\r\n% <https:\/\/blogs.mathworks.com\/developer\/2020\/08\/19\/tests-in-projects\/\r\n% here> that putting this in the form of a MATLAB project helps get our\r\n% environment setup correctly. So our command is just a quick opening of\r\n% the project and a call to the nifty |compileAll| function. Here we use\r\n% the |runMATLABCommand| step that comes as part of the plugin. We don't\r\n% need to worry about how MATLAB launches (which varies with different\r\n% releases), we just worry about the command we need to invoke. It's as\r\n% easy as adding a stage with this step inside of it:\r\n%\r\n%   stage('Compile MEX files') {\r\n%       steps {\r\n%           runMATLABCommand 'openProject(pwd); compileAll'\r\n%       }\r\n%   }\r\n%\r\n% *Step 3*\r\n%\r\n% Now let's run the tests! Last time we touched on this we ran all the\r\n% tests defined in the project and we generated test results in the TAP\r\n% format and code coverage in the Cobertura format. I am going to make a\r\n% slight tweak here, because the TAPPlugin, which processes these TAP\r\n% results, doesn't support Jenkins pipelines as well as the JUnit plugin.\r\n% As you might be starting to see, this can be done with some\r\n% straightforward pipeline syntax that matches the same kind of thing we\r\n% can do through the UI.\r\n%\r\n%   stage('Run MATLAB tests') {\r\n%       steps {\r\n%           runMATLABTests(\r\n%               testResultsJUnit: 'matlabTestArtifacts\/junittestreport.xml',\r\n%               codeCoverageCobertura: 'matlabTestArtifacts\/cobertura.xml'\r\n%           )\r\n%       }\r\n%   }\r\n% \r\n% *Step 4*\r\n%\r\n% ...and finally, now that we have built our project, tested it and\r\n% generated some test results and coverage artifacts, we can now process\r\n% those artifacts using other plugins meant for that purpose (the\r\n% <https:\/\/plugins.jenkins.io\/junit\/ JUnit plugin> and the\r\n% <https:\/\/plugins.jenkins.io\/code-coverage-api\/ Code Coverage API plugin>).\r\n% I'll just add them to the same stage as the test run.\r\n% \r\n%   stage('Run MATLAB tests') {\r\n%       steps {\r\n%           runMATLABTests(\r\n%               testResultsJUnit: 'matlabTestArtifacts\/junittestreport.xml',\r\n%               codeCoverageCobertura: 'matlabTestArtifacts\/cobertura.xml'\r\n%           )\r\n%           junit 'matlabTestArtifacts\/junittestreport.xml'\r\n%           publishCoverage adapters: [coberturaAdapter('matlabTestArtifacts\/cobertura.xml')]\r\n%       }\r\n%   }\r\n% \r\n% That's it, that's all we need. Here is the whole Jenkinsfile for reference:\r\n%\r\n%   pipeline {\r\n%       agent any\r\n%       environment {\r\n%           PATH = \"\/Applications\/MATLAB_R2020b.app\/bin:${PATH}\"\r\n%       }\r\n%       stages {\r\n%           stage('Compile MEX files') {\r\n%               steps {\r\n%                   runMATLABCommand 'openProject(pwd); compileAll'\r\n%               }\r\n%           }\r\n%           stage('Run MATLAB tests') {\r\n%               steps {\r\n%                   runMATLABTests(\r\n%                       testResultsJUnit: 'matlabTestArtifacts\/junittestreport.xml',\r\n%                       codeCoverageCobertura: 'matlabTestArtifacts\/cobertura.xml'\r\n%                       )\r\n%                   junit 'matlabTestArtifacts\/junittestreport.xml'\r\n%                   publishCoverage adapters: [coberturaAdapter('matlabTestArtifacts\/cobertura.xml')]\r\n%               }\r\n%           }\r\n%       }\r\n%   }\r\n% \r\n% Now just check something like that into the root of your repository, and\r\n% you will be good to go. Here's how you create a Jenkins job that uses it:\r\n% \r\n% <<y2021NewPipelineProject.gif>>\r\n%\r\n% ...and now you can see the pipeline runs just fine and dandy as it pulls\r\n% its pipeline instructions from the Jenkinsfile instead of the Jenkins UI:\r\n%\r\n% <<y2021RunPipelineProject.gif>>\r\n%\r\n% ...and now since this pipeline configuration is checked in, it travels with\r\n% the code, it can be reverted with the code, it can be reviewed with the\r\n% code, and it can be reasoned with the code. Code is beautiful, especially \r\n% when backed by a little bit of source control, MATLAB projects, and what\r\n% we hope are useful CI platform integrations.\r\n%\r\n% Now let's all burst out in song!!\r\n##### SOURCE END ##### b0e1e9978d51451bba2b7f8bc96f48f6\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/developer\/files\/y2021-Dark-Stormy.jpg\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><!--\/introduction-->... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/developer\/2021\/02\/18\/configuration-as-code\/\">read more >><\/a><\/p>","protected":false},"author":90,"featured_media":2578,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[4,20,7],"tags":[],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/posts\/2568"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/users\/90"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/comments?post=2568"}],"version-history":[{"count":5,"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/posts\/2568\/revisions"}],"predecessor-version":[{"id":2586,"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/posts\/2568\/revisions\/2586"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/media\/2578"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/media?parent=2568"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/categories?post=2568"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/developer\/wp-json\/wp\/v2\/tags?post=2568"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}