{"id":9688,"date":"2019-02-14T01:22:25","date_gmt":"2019-02-14T01:22:25","guid":{"rendered":"http:\/\/www.appservgrid.com\/paw92\/?p=9688"},"modified":"2019-02-14T01:22:25","modified_gmt":"2019-02-14T01:22:25","slug":"rstudio-connect-deployments-with-github-webhooks-and-jenkins","status":"publish","type":"post","link":"https:\/\/www.appservgrid.com\/paw92\/index.php\/2019\/02\/14\/rstudio-connect-deployments-with-github-webhooks-and-jenkins\/","title":{"rendered":"RStudio Connect Deployments with GitHub Webhooks and Jenkins"},"content":{"rendered":"<section class=\"section section--body section--first\">\n<div class=\"section-content\">\n<div class=\"section-inner sectionLayout--insetColumn\">\n<p id=\"727a\" class=\"graf graf--p graf-after--h3\"><em class=\"markup--em markup--p-em\">New content management Connect server APIs are easy to integrate with programmatic deployment workflows.<\/em><\/p>\n<p id=\"1988\" class=\"graf graf--p graf-after--p\"><a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/blog.rstudio.com\/2019\/01\/17\/announcing-rstudio-connect-1-7-0\/\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/blog.rstudio.com\/2019\/01\/17\/announcing-rstudio-connect-1-7-0\/\">Have you heard<\/a>!? RStudio Connect 1.7.0 has support for\u00a0<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/docs.rstudio.com\/connect\/1.7.0\/user\/cookbook.html#cookbook-deploying\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/docs.rstudio.com\/connect\/1.7.0\/user\/cookbook.html#cookbook-deploying\">programmatic deployment<\/a>\u00a0in the\u00a0<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/docs.rstudio.com\/connect\/1.7.0\/api\/#content\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/docs.rstudio.com\/connect\/1.7.0\/api\/#content\">RStudio Connect Server API<\/a>. These new APIs let your deployment engineers craft custom deployment workflows like these:<\/p>\n<figure id=\"64e1\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*mFZ646wY-a2sKkAo3BTvjw.png\" data-width=\"1448\" data-height=\"548\" data-action=\"zoom\" data-action-value=\"1*mFZ646wY-a2sKkAo3BTvjw.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"27\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*mFZ646wY-a2sKkAo3BTvjw.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*mFZ646wY-a2sKkAo3BTvjw.png\" \/><\/div>\n<\/div>\n<\/figure>\n<p id=\"c1a3\" class=\"graf graf--p graf-after--figure\">This article demonstrates programmatic deployment of a Shiny application with GitHub webhooks and a Jenkins Freestyle project.<\/p>\n<h3 id=\"15c7\" class=\"graf graf--h3 graf-after--p\">What are we trying to\u00a0build?<\/h3>\n<p id=\"a7e4\" class=\"graf graf--p graf-after--h3\">I have a data product (in this case a shiny application) deployed to my RStudio Connect server. I also have a GitHub repository for the application where I\u2019ve version controlled the app code. I want to link and automate the application update process with my GitHub workflow, i.e. every time I push a code change to GitHub, I\u2019d like the deployed application on Connect to automatically be updated with those changes.<\/p>\n<figure id=\"3ceb\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*6iBXFafwvl1ztoU5P8_40Q.png\" data-width=\"1338\" data-height=\"452\" data-action=\"zoom\" data-action-value=\"1*6iBXFafwvl1ztoU5P8_40Q.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"25\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*6iBXFafwvl1ztoU5P8_40Q.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*6iBXFafwvl1ztoU5P8_40Q.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">Basic Build\u00a0Plan<\/figcaption><\/figure>\n<p id=\"1652\" class=\"graf graf--p graf-after--figure\">This workflow assumes that the content has already been deployed to my Connect Server at least once. The initial deployment could be achieved programmatically or through traditional\u00a0<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/docs.rstudio.com\/connect\/user\/publishing.html\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/docs.rstudio.com\/connect\/user\/publishing.html\">IDE push-button \/ rsconnect deployment<\/a>\u00a0methods. The content management API for RStudio Connect can be leveraged to perform the initial bundle upload programmatically.<\/p>\n<p id=\"4530\" class=\"graf graf--p graf-after--p\">To read more about the content management APIs and view existing recipes, please see the following resources:<\/p>\n<ul class=\"postList\">\n<li id=\"8990\" class=\"graf graf--li graf-after--p\"><a class=\"markup--anchor markup--li-anchor\" href=\"https:\/\/docs.rstudio.com\/connect\/api\/\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/docs.rstudio.com\/connect\/api\/\">RStudio Connect API Reference<\/a><\/li>\n<li id=\"53f5\" class=\"graf graf--li graf-after--li\"><a class=\"markup--anchor markup--li-anchor\" href=\"https:\/\/docs.rstudio.com\/connect\/user\/cookbook.html#recipes\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/docs.rstudio.com\/connect\/user\/cookbook.html#recipes\">User Guide API Cookbook<\/a>\u00a0(Deploying Content)<\/li>\n<li id=\"b063\" class=\"graf graf--li graf-after--li\"><a class=\"markup--anchor markup--li-anchor\" href=\"https:\/\/github.com\/rstudio\/connect-api-deploy-shiny\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/github.com\/rstudio\/connect-api-deploy-shiny\">Example deployment scripts and workflow considerations<\/a><\/li>\n<\/ul>\n<p id=\"6fe3\" class=\"graf graf--p graf-after--li\">After reviewing the API documentation and example scripts, flesh out your plan to include actionable steps and tools. My updated diagram shows each process and the required resources for defining functional automation:<\/p>\n<figure id=\"2eb2\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*f0iV_bA9zP7YKYqW_oDZ5Q.png\" data-width=\"1338\" data-height=\"452\" data-is-featured=\"true\" data-action=\"zoom\" data-action-value=\"1*f0iV_bA9zP7YKYqW_oDZ5Q.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"25\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*f0iV_bA9zP7YKYqW_oDZ5Q.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*f0iV_bA9zP7YKYqW_oDZ5Q.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">Actual Build\u00a0Plan<\/figcaption><\/figure>\n<p id=\"19e5\" class=\"graf graf--p graf-after--figure\"><em class=\"markup--em markup--p-em\">Note: I started this project with a brand new, clean Jenkins Server. I use Ansible to create (and tear down) small Jenkins servers that live for the duration of my experiments. This article will not cover the basics of installing and configuring a Jenkins server.<\/em><\/p>\n<h3 id=\"3976\" class=\"graf graf--h3 graf-after--p\">Development and Git Branching<\/h3>\n<p id=\"609a\" class=\"graf graf--p graf-after--h3\">Application development occurs in the RStudio IDE. I plan to create a\u00a0<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/happygitwithr.com\/git-branches.html\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/happygitwithr.com\/git-branches.html\">git branching strategy<\/a>\u00a0so that new changes can be kept separate from the master branch and reviewed before merging. The GitHub repository I created to keep the application code can be viewed here:<\/p>\n<p id=\"0fde\" class=\"graf graf--p graf-after--p\"><a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/github.com\/kellobri\/basic-deploy-app\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/github.com\/kellobri\/basic-deploy-app\">GitHub repository for the Shiny application<\/a><br \/>\n&#8211; deployment-bundle:\u00a0<code class=\"markup--code markup--p-code\">app.R<\/code>,\u00a0<code class=\"markup--code markup--p-code\">manifest.json<\/code><br \/>\n&#8211;\u00a0<code class=\"markup--code markup--p-code\">README.md<\/code><\/p>\n<p id=\"c9d5\" class=\"graf graf--p graf-after--p\">This repository contains a README file (not required) and a single directory with all the application code (in this case only an\u00a0<code class=\"markup--code markup--p-code\">app.R<\/code>\u00a0file) as well as the manifest file which can be generated with the\u00a0<code class=\"markup--code markup--p-code\">rsconnect<\/code>\u00a0package in the RStudio IDE:\u00a0<code class=\"markup--code markup--p-code\">rsconnect::rsconnect::writeManifest()<\/code><\/p>\n<h3 id=\"3d2e\" class=\"graf graf--h3 graf-after--p\">GitHub Webhooks<\/h3>\n<p id=\"120d\" class=\"graf graf--p graf-after--h3\">The next step of GitHub set up is to create a\u00a0<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.github.com\/webhooks\/\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/developer.github.com\/webhooks\/\">Webhook<\/a>\u00a0so that the Jenkins server can be notified of all new changes to Master.<\/p>\n<p id=\"4ef9\" class=\"graf graf--p graf-after--p\">In the GitHub repository, navigate to the\u00a0<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">Settings<\/em><\/strong>\u00a0page, then select\u00a0<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">Webhooks<\/em><\/strong>from the left sidebar. Add a new webhook to see the management form as shown here:<\/p>\n<figure id=\"6e53\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*RMA8XTbfDnE3126CZxjDBw.png\" data-width=\"2674\" data-height=\"1142\" data-action=\"zoom\" data-action-value=\"1*RMA8XTbfDnE3126CZxjDBw.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"30\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*RMA8XTbfDnE3126CZxjDBw.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*RMA8XTbfDnE3126CZxjDBw.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">Create a new webhook for\u00a0Jenkins<\/figcaption><\/figure>\n<p id=\"45c1\" class=\"graf graf--p graf-after--figure\">For the Payload URL field, provide the URL of your Jenkins server with\u00a0<code class=\"markup--code markup--p-code\">\/github-webhook\/<\/code>\u00a0appended to it. These are the selections I set for the webhook:<\/p>\n<p id=\"b95f\" class=\"graf graf--p graf-after--p\">Payload URL:\u00a0<code class=\"markup--code markup--p-code\"><a class=\"markup--anchor markup--p-anchor\" href=\"http:\/\/ec2-3-85-14-228.compute-1.amazonaws.com:8080\/github-webhook\/\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"http:\/\/ec2-3-85-14-228.compute-1.amazonaws.com:8080\/github-webhook\/\">http:\/\/[EXAMPLE-AWS-INSTANCE]\/github-webhook\/<\/a><\/code><br \/>\nContent type:\u00a0<code class=\"markup--code markup--p-code\">application\/json<\/code><br \/>\nSecret: [blank]\u200a\u2014\u200aI did not use this<br \/>\nEvent triggers: Just the push event<br \/>\nActive: Check<\/p>\n<h3 id=\"9649\" class=\"graf graf--h3 graf-after--p\">Jenkins GitHub Integration Plugin<\/h3>\n<p id=\"b8bb\" class=\"graf graf--p graf-after--h3\">Now that the webhook is in place, the next step is to configure the receiving end. Jenkins needs the GitHub Integration plugin to receive POST payloads coming from GitHub every time the push event triggers.<\/p>\n<p id=\"7200\" class=\"graf graf--p graf-after--p\">Add the GitHub plugin to Jenkins:<\/p>\n<ul class=\"postList\">\n<li id=\"3d47\" class=\"graf graf--li graf-after--p\">Manage Jenkins &gt; Manage Plugins<\/li>\n<\/ul>\n<figure id=\"d06c\" class=\"graf graf--figure graf-after--li\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*mxKS5Y8uZmgE3G83IPhd5Q.png\" data-width=\"2596\" data-height=\"1178\" data-action=\"zoom\" data-action-value=\"1*mxKS5Y8uZmgE3G83IPhd5Q.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"32\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*mxKS5Y8uZmgE3G83IPhd5Q.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*mxKS5Y8uZmgE3G83IPhd5Q.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">Manage Jenkins\u00a0Plugins<\/figcaption><\/figure>\n<p id=\"11be\" class=\"graf graf--p graf-after--figure\">Check the\u00a0<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">Installed<\/em><\/strong>\u00a0tab to see if the GitHub Integration Plugin already exists. If not, search for it in the\u00a0<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">Available<\/em><\/strong>\u00a0tab, download and install.<\/p>\n<h3 id=\"338d\" class=\"graf graf--h3 graf-after--p\">Docker in\u00a0Jenkins<\/h3>\n<p id=\"534b\" class=\"graf graf--p graf-after--h3\">In order to streamline the deployment build process for this project, I\u2019ve chosen to use Docker image provided in the programmatic deployment example repo provided here:\u00a0<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/github.com\/rstudio\/connect-api-deploy-shiny\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/github.com\/rstudio\/connect-api-deploy-shiny\">rstudio\/connect-api-deploy-shiny<\/a>.<\/p>\n<p id=\"fcf2\" class=\"graf graf--p graf-after--p\">There are many ways to incorporate the use of Docker containers into Jenkins projects. Rather than leverage an eternal container registry and a Jenkins-Docker plugin, I\u2019ll show quick-and-dirty way, invoking it directly with shell commands.<\/p>\n<p id=\"a8f7\" class=\"graf graf--p graf-after--p\"><em class=\"markup--em markup--p-em\">Note: My Jenkins server is built with the Docker service installed, so this will work for my project, but it might not work for yours. Take the time to investigate what Docker integrations exist and are considered best practices if you are working on a shared or pre-existing Jenkins installation.<\/em><\/p>\n<p id=\"0621\" class=\"graf graf--p graf-after--p\">In a second GitHub repository, I\u2019ve version controlled all the pieces of the deployment environment as well as the\u00a0<code class=\"markup--code markup--p-code\">upload-and-deploy.sh<\/code>\u00a0script that will be used to interact with the RStudio Connect content management API. This repository is separate from the Shiny application code repo so that I can have a singular, centralized location for keeping just these pieces of the process.<\/p>\n<p id=\"9037\" class=\"graf graf--p graf-after--p\"><a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/github.com\/kellobri\/prog-deploy-jenkins\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/github.com\/kellobri\/prog-deploy-jenkins\">GitHub repository for the dockerfile and deployment scripts<\/a>:<br \/>\n&#8211; docker:\u00a0<code class=\"markup--code markup--p-code\">Dockerfile<\/code><br \/>\n&#8211;\u00a0<code class=\"markup--code markup--p-code\">upload-and-deploy.sh<\/code>\u00a0(modified from\u00a0<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/github.com\/rstudio\/connect-api-deploy-shiny\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/github.com\/rstudio\/connect-api-deploy-shiny\">rstudio\/connect-api-deploy-shiny<\/a>)<br \/>\n&#8211;\u00a0<code class=\"markup--code markup--p-code\">README.md<\/code><\/p>\n<h3 id=\"2564\" class=\"graf graf--h3 graf-after--p\">Create a Jenkins\u00a0Project<\/h3>\n<p id=\"e3ac\" class=\"graf graf--p graf-after--h3\">All the parts are in place, so finally it\u2019s time to put everything together in Jenkins.<\/p>\n<p id=\"546c\" class=\"graf graf--p graf-after--p\">Start a\u00a0<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">New Item<\/em><\/strong>\u00a0&gt;\u00a0<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">Freestyle Project<\/em><\/strong><\/p>\n<ul class=\"postList\">\n<li id=\"c982\" class=\"graf graf--li graf-after--p\">Give your project a name (e.g. \u201cbasic-app-deploy\u201d)<\/li>\n<li id=\"2582\" class=\"graf graf--li graf-after--li\">I plan on linking this project to only one piece of content, so the name of the Jenkins project can reference my specific Shiny application.<\/li>\n<\/ul>\n<figure id=\"e0a4\" class=\"graf graf--figure graf-after--li\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*V4sS07E4UaGx3n9Ty4z1mw.png\" data-width=\"1974\" data-height=\"782\" data-action=\"zoom\" data-action-value=\"1*V4sS07E4UaGx3n9Ty4z1mw.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"27\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*V4sS07E4UaGx3n9Ty4z1mw.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*V4sS07E4UaGx3n9Ty4z1mw.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">Start a Freestyle Project<\/figcaption><\/figure>\n<h4 id=\"d37a\" class=\"graf graf--h4 graf-after--figure\"><strong class=\"markup--strong markup--h4-strong\"><em class=\"markup--em markup--h4-em\">Sidebar: Why Jenkins Freestyle?<\/em><\/strong><\/h4>\n<p id=\"f73b\" class=\"graf graf--p graf-after--h4\">If there were a crawl-walk-run strategy for working with Jenkins, Freestyle projects might be the crawl step. If you\u2019re already familiar with Jenkins, you might be more interested in setting up a pipeline project or using a Jenkinsfile to structure the workflow.<\/p>\n<p id=\"5525\" class=\"graf graf--p graf-after--p\">Pros and Cons of Jenkins Freestyle:<\/p>\n<p id=\"b696\" class=\"graf graf--p graf-after--p\">Pro: New to Jenkins\u00a0?\u2014 low learning curve<br \/>\nPro: Nice way to learn the Jenkins web interface<br \/>\nPro: Quick way to accomplish simple jobs (this is not a complex build)<\/p>\n<p id=\"bbf1\" class=\"graf graf--p graf-after--p\">Con: Way too much clicking through web forms<br \/>\nCon: Job is not defined as code<\/p>\n<h4 id=\"519c\" class=\"graf graf--h4 graf-after--p\">Navigate the Freestyle Project\u00a0Webform<\/h4>\n<p id=\"248d\" class=\"graf graf--p graf-after--h4\">Once you have a new project set up, step through the freestyle webform complete the configuration:<\/p>\n<p id=\"2103\" class=\"graf graf--p graf-after--p\"><strong class=\"markup--strong markup--p-strong\">General<\/strong><\/p>\n<ul class=\"postList\">\n<li id=\"f5f5\" class=\"graf graf--li graf-after--p\">Check:\u00a0<em class=\"markup--em markup--li-em\">GitHub project<\/em><br \/>\nProject url:\u00a0<a class=\"markup--anchor markup--li-anchor\" href=\"https:\/\/github.com\/kellobri\/basic-deploy-app.git\/\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/github.com\/kellobri\/basic-deploy-app.git\/\">https:\/\/github.com\/kellobri\/basic-deploy-app.git\/<\/a><\/li>\n<\/ul>\n<p id=\"e3d3\" class=\"graf graf--p graf-after--li\"><strong class=\"markup--strong markup--p-strong\">Source Code Management<\/strong><\/p>\n<ul class=\"postList\">\n<li id=\"3ed1\" class=\"graf graf--li graf-after--p\"><em class=\"markup--em markup--li-em\">Git<\/em>\u200a\u2014\u200aRepositories:<br \/>\n&#8211; Repository URL:\u00a0<a class=\"markup--anchor markup--li-anchor\" href=\"https:\/\/github.com\/kellobri\/basic-deploy-app.git\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/github.com\/kellobri\/basic-deploy-app.git\">https:\/\/github.com\/kellobri\/basic-deploy-app.git<\/a><br \/>\n&#8211; Branches to build: *\/master<\/li>\n<\/ul>\n<p id=\"4b8d\" class=\"graf graf--p graf-after--li\"><strong class=\"markup--strong markup--p-strong\">Build Triggers<\/strong><\/p>\n<ul class=\"postList\">\n<li id=\"5eca\" class=\"graf graf--li graf-after--p\">Check:\u00a0<em class=\"markup--em markup--li-em\">GitHub hook trigger for GITScm polling<\/em><\/li>\n<\/ul>\n<p id=\"23d5\" class=\"graf graf--p graf-after--li\"><strong class=\"markup--strong markup--p-strong\">Build Environment &gt; Bindings<\/strong><\/p>\n<p id=\"0703\" class=\"graf graf--p graf-after--p\">Programmatic deployment requires an RStudio Connect API key. Generate an API key through the Connect user interface:<\/p>\n<figure id=\"ed45\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*4HB6lpvMnxrG7G0hI827FA.png\" data-width=\"1932\" data-height=\"626\" data-action=\"zoom\" data-action-value=\"1*4HB6lpvMnxrG7G0hI827FA.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"22\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*4HB6lpvMnxrG7G0hI827FA.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*4HB6lpvMnxrG7G0hI827FA.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">RStudio Connect API\u00a0Keys<\/figcaption><\/figure>\n<p id=\"ae6c\" class=\"graf graf--p graf-after--figure\">Add Credentials: Save the API key as a\u00a0<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">Secret Text<\/em><\/strong>\u00a0in Jenkins Credentials Provider:<\/p>\n<p id=\"d502\" class=\"graf graf--p graf-after--p\">You can expose secret texts to the Build Environment through the Bindings option:<\/p>\n<ul class=\"postList\">\n<li id=\"30b4\" class=\"graf graf--li graf-after--p\">Check: Use secret texts or files<br \/>\nSecret text:<br \/>\n&#8211; Variable: PUBLISHER_KEY (choose a name)<\/li>\n<li id=\"2a93\" class=\"graf graf--li graf-after--li\">Credentials: Add &gt; Jenkins &gt; Add Credentials<\/li>\n<\/ul>\n<figure id=\"b801\" class=\"graf graf--figure graf-after--li\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*ZF2UXnUx33AacWG4T4QyNw.png\" data-width=\"2176\" data-height=\"974\" data-action=\"zoom\" data-action-value=\"1*ZF2UXnUx33AacWG4T4QyNw.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"32\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*ZF2UXnUx33AacWG4T4QyNw.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*ZF2UXnUx33AacWG4T4QyNw.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">Save the API key as a\u00a0<strong class=\"markup--strong markup--figure-strong\"><em class=\"markup--em markup--figure-em\">Secret Text<\/em><\/strong>\u00a0in Jenkins Credentials Provider<\/figcaption><\/figure>\n<p id=\"201e\" class=\"graf graf--p graf-after--figure\"><strong class=\"markup--strong markup--p-strong\">Build<\/strong><\/p>\n<p id=\"2ce0\" class=\"graf graf--p graf-after--p\">The build pane allows for many different types of task selections. For simplicity, I chose the\u00a0<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">Execute Shell Commands<\/em><\/strong>\u00a0option. I created three blocks of Shell Command build tasks, but the separation was only for readability:<\/p>\n<p id=\"650d\" class=\"graf graf--p graf-after--p\"><strong class=\"markup--strong markup--p-strong\">Execute Shell Block 1<\/strong>: Read in the Dockerfile and deployment shell script from GitHub<\/p>\n<pre id=\"4a21\" class=\"graf graf--pre graf-after--p\">rm -rf prog-deploy-jenkins\r\ngit clone <a class=\"markup--anchor markup--pre-anchor\" href=\"https:\/\/github.com\/kellobri\/prog-deploy-jenkins.git\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/github.com\/kellobri\/prog-deploy-jenkins.git\">https:\/\/github.com\/kellobri\/prog-deploy-jenkins.git<\/a>\r\nstat prog-deploy-jenkins\/docker\/Dockerfile\r\nchmod 755 prog-deploy-jenkins\/upload-and-deploy.sh<\/pre>\n<p id=\"c5c1\" class=\"graf graf--p graf-after--pre\"><strong class=\"markup--strong markup--p-strong\">Execute Shell Block 2:<\/strong>\u00a0Build the Docker image<\/p>\n<pre id=\"733f\" class=\"graf graf--pre graf-after--p\">cd prog-deploy-jenkins\/\r\ndocker build -t rstudio-connect-deployer:latest docker<\/pre>\n<p id=\"db92\" class=\"graf graf--p graf-after--pre\"><strong class=\"markup--strong markup--p-strong\">Execute Shell Block 3:<\/strong>\u00a0Run the Docker container and deployment script<\/p>\n<pre id=\"5ed7\" class=\"graf graf--pre graf-after--p\">docker run --rm \\\r\n--privileged=true \\\r\n-e CONNECT_SERVER=\"http:\/\/ec2-52-90-255-153.compute-1.amazonaws.com:3939\/\" \\\r\n-e CONNECT_API_KEY=$PUBLISHER_KEY \\\r\n-v $(pwd):\/content \\\r\n-w \/content \\\r\nrstudio-connect-deployer:latest \\\r\n\/content\/prog-deploy-jenkins\/upload-and-deploy.sh 5c276b83-2eeb-427b-95a6-ac8915e22bfd \/content\/deployment-bundle<\/pre>\n<p id=\"611c\" class=\"graf graf--p graf-after--pre\">In this block, I reference the\u00a0<code class=\"markup--code markup--p-code\">PUBLISHER_KEY<\/code>\u00a0credential created in the Build Environment step earlier.<\/p>\n<figure id=\"a5f1\" class=\"graf graf--figure graf--layoutOutsetLeft graf-after--p\" data-scroll=\"native\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*e5-5sJj8Vnoo5016VjSFQQ.png\" data-width=\"710\" data-height=\"752\" data-action=\"zoom\" data-action-value=\"1*e5-5sJj8Vnoo5016VjSFQQ.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"72\" height=\"75\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/600\/1*e5-5sJj8Vnoo5016VjSFQQ.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/600\/1*e5-5sJj8Vnoo5016VjSFQQ.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">Content GUID\u00a0lookup<\/figcaption><\/figure>\n<p id=\"957f\" class=\"graf graf--p graf-after--figure\">I have also hard-coded two additional important pieces of information: the\u00a0<code class=\"markup--code markup--p-code\">CONNECT_SERVER<\/code>\u00a0address, and the application GUID. You could easily create a secret text credential for the server address like we made for the the API key. The application GUID is an identifying piece of information that you\u2019ll have to look up from the RStudio Connect User Interface.<\/p>\n<p id=\"b293\" class=\"graf graf--p graf-after--p\">The app GUID is listed at the bottom of the\u00a0<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">Info<\/em><\/strong>\u00a0settings tab of the deployed content on RStudio Connect.<\/p>\n<p id=\"a1e7\" class=\"graf graf--p graf-after--p\"><strong class=\"markup--strong markup--p-strong\">Project Finishing Touches:<\/strong><\/p>\n<ul class=\"postList\">\n<li id=\"cca2\" class=\"graf graf--li graf-after--p\">Save your Jenkins freestyle project<\/li>\n<li id=\"c312\" class=\"graf graf--li graf-after--li\">Test it by pushing a change to GitHub!<\/li>\n<\/ul>\n<figure id=\"6c58\" class=\"graf graf--figure graf--iframe graf-after--li\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<p><iframe loading=\"lazy\" title=\"RStudio Connect: Jenkins + GitHub Webhooks Demo\" width=\"525\" height=\"295\" src=\"https:\/\/www.youtube.com\/embed\/FMaThMe5BhE?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<\/div>\n<\/div><figcaption class=\"imageCaption\">Demo of a Successful Test<\/figcaption><\/figure>\n<h3 id=\"8bea\" class=\"graf graf--h3 graf-after--figure\">Useful Jenkins Debugging Areas<\/h3>\n<p id=\"18d8\" class=\"graf graf--p graf-after--h3\">From the Jenkins dashboard, click on your project. Here you can go back to the webform and change something by clicking the \u2018Configure\u2019 link. To see details about the last build, click on that build link; from here you can access the console output for the build\u200a\u2014\u200athis is usually the first place I go when a build fails.<\/p>\n<figure id=\"a9ac\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*oL68xVNs-4equhYFetGP2w.png\" data-width=\"1928\" data-height=\"816\" data-action=\"zoom\" data-action-value=\"1*oL68xVNs-4equhYFetGP2w.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"30\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*oL68xVNs-4equhYFetGP2w.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*oL68xVNs-4equhYFetGP2w.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">Console Output for Jenkins Debugging<\/figcaption><\/figure>\n<p id=\"1cc6\" class=\"graf graf--p graf-after--figure\">Also great for iteration and debugging: You can always schedule and run build tests directly from Jenkins without pushing random code changes to GitHub.<\/p>\n<h3 id=\"bc3c\" class=\"graf graf--h3 graf-after--p\">Success\u200a\u2014\u200aWhat\u2019s\u00a0Next?<\/h3>\n<p id=\"2adc\" class=\"graf graf--p graf-after--h3\">Congrats! Here are some places to explore next:<\/p>\n<h4 id=\"25a9\" class=\"graf graf--h4 graf-after--p\">What if I need to do this for five more shiny\u00a0apps?<\/h4>\n<p id=\"2368\" class=\"graf graf--p graf-after--h4\">Use this working freestyle project as a template for a new project!<\/p>\n<p id=\"8cfc\" class=\"graf graf--p graf-after--p\">From the Jenkins Dashboard, Select:<strong class=\"markup--strong markup--p-strong\"><em class=\"markup--em markup--p-em\">\u00a0New Item<\/em><\/strong>\u00a0&gt; Name the project &gt; Then scroll to the bottom of the project type selection options and use auto-complete to find the project you\u2019d like to copy from:<\/p>\n<figure id=\"7744\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*J6dC6qxNUoWGsTvq36quwA.png\" data-width=\"1926\" data-height=\"502\" data-action=\"zoom\" data-action-value=\"1*J6dC6qxNUoWGsTvq36quwA.png\" data-scroll=\"native\"><canvas class=\"progressiveMedia-canvas js-progressiveMedia-canvas\" width=\"75\" height=\"17\"><\/canvas><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*J6dC6qxNUoWGsTvq36quwA.png\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*J6dC6qxNUoWGsTvq36quwA.png\" \/><\/div>\n<\/div><figcaption class=\"imageCaption\">Use your first project as a template for\u00a0others<\/figcaption><\/figure>\n<h4 id=\"1e5e\" class=\"graf graf--h4 graf-after--figure\">Great\u200a\u2014\u200aBut what if I need to do this for 100 more shiny\u00a0apps?<\/h4>\n<p id=\"88b8\" class=\"graf graf--p graf-after--h4\">Remember that crawl-walk-run strategy that I mentioned earlier? If you need to put CI\/CD in place for 100 shiny applications, you\u2019re probably going to want to consider some of the other methods for interacting with Jenkins.<\/p>\n<p id=\"e3b7\" class=\"graf graf--p graf-after--p\">Freestyle projects are a great learning tool\u200a\u2014\u200aand can be helpful for getting small projects off the ground quickly. But I wouldn\u2019t recommend using them long term unless clicking around in webforms is your\u00a0<em class=\"markup--em markup--p-em\">favorite thing ever<\/em>.<\/p>\n<p id=\"d7b7\" class=\"graf graf--p graf-after--p graf--trailing\">If you\u2019re looking to do large-scale programmatic deployments with Jenkins, I recommend moving toward a workflow structured on pipeline projects and Jenkinsfiles.<\/p>\n<\/div>\n<\/div>\n<\/section>\n<section class=\"section section--body section--last\">\n<div class=\"section-divider\">\n<hr class=\"section-divider\" \/>\n<\/div>\n<div class=\"section-content\">\n<div class=\"section-inner sectionLayout--insetColumn\">\n<p id=\"62c1\" class=\"graf graf--p graf--leading\">Key Resources in this Article:<\/p>\n<ul class=\"postList\">\n<li id=\"8695\" class=\"graf graf--li graf-after--p\"><a class=\"markup--anchor markup--li-anchor\" href=\"https:\/\/docs.rstudio.com\/connect\/api\/\" target=\"_blank\" rel=\"noopener nofollow\" data-href=\"https:\/\/docs.rstudio.com\/connect\/api\/\">RStudio Connect API Reference<\/a><\/li>\n<li id=\"22f8\" class=\"graf graf--li graf-after--li\"><a class=\"markup--anchor markup--li-anchor\" href=\"https:\/\/docs.rstudio.com\/connect\/user\/cookbook.html#recipes\" target=\"_blank\" rel=\"noopener nofollow\" data-href=\"https:\/\/docs.rstudio.com\/connect\/user\/cookbook.html#recipes\">User Guide API Cookbook<\/a>\u00a0(Deploying Content)<\/li>\n<li id=\"5999\" class=\"graf graf--li graf-after--li\"><a class=\"markup--anchor markup--li-anchor\" href=\"https:\/\/github.com\/rstudio\/connect-api-deploy-shiny\" target=\"_blank\" rel=\"noopener nofollow\" data-href=\"https:\/\/github.com\/rstudio\/connect-api-deploy-shiny\">Example deployment scripts and workflow considerations<\/a><\/li>\n<\/ul>\n<p id=\"c513\" class=\"graf graf--p graf-after--li graf--trailing\">RStudio Community is a great place to start conversations and share your ideas about how to grow and adapt these workflows.<\/p>\n<\/div>\n<\/div>\n<\/section>\n","protected":false},"excerpt":{"rendered":"<p>New content management Connect server APIs are easy to integrate with programmatic deployment workflows. Have you heard!? RStudio Connect 1.7.0 has support for\u00a0programmatic deployment\u00a0in the\u00a0RStudio Connect Server API. These new APIs let your deployment engineers craft custom deployment workflows like these: This article demonstrates programmatic deployment of a Shiny application with GitHub webhooks and a &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.appservgrid.com\/paw92\/index.php\/2019\/02\/14\/rstudio-connect-deployments-with-github-webhooks-and-jenkins\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;RStudio Connect Deployments with GitHub Webhooks and Jenkins&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-9688","post","type-post","status-publish","format-standard","hentry","category-linux"],"_links":{"self":[{"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/posts\/9688","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/comments?post=9688"}],"version-history":[{"count":1,"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/posts\/9688\/revisions"}],"predecessor-version":[{"id":9689,"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/posts\/9688\/revisions\/9689"}],"wp:attachment":[{"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/media?parent=9688"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/categories?post=9688"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.appservgrid.com\/paw92\/index.php\/wp-json\/wp\/v2\/tags?post=9688"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}