<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>git on Digi Hunch</title><link>https://www.digihunch.com/tag/git/</link><description>Recent content in git on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Mon, 12 May 2025 23:27:51 -0400</lastBuildDate><atom:link href="https://www.digihunch.com/tag/git/index.xml" rel="self" type="application/rss+xml"/><item><title>GitHub Action Gotchas</title><link>https://www.digihunch.com/2023/01/github-action-gotchas/</link><pubDate>Fri, 27 Jan 2023 01:15:02 -0400</pubDate><guid>https://www.digihunch.com/2023/01/github-action-gotchas/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/gh-action-feature.webp" alt="Featured image of post GitHub Action Gotchas" /&gt;&lt;p class="wp-block-paragraph"&gt;I &lt;a href="https://www.digihunch.com/2021/05/getting-started-with-github-actions/"&gt;started with GitHub Actions&lt;/a&gt; a couple years ago. Recently I came across a few interesting use cases while I was trying to setup Terraform workflow with GitHub actions. These use cases prompted me to make use some new features in GitHub Action. So I put them in a post here.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-runners-can-assume-iam-role-in-aws"&gt;Runners can assume IAM Role in AWS&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In many scenarios we want to execute AWS CLI command from GitHub action. Also, executables such as terraform inherits credential from AWS CLI. The credential should be a temporary role-based credential instead of an IAM user based on access keys. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There is a GitHub Action called &lt;a href="https://github.com/marketplace/actions/configure-aws-credentials-for-github-actions"&gt;configure-aws-credentials-for-github-actions&lt;/a&gt; that can help configure GitHub runner using OIDC identity provider (since Nov 2021 v1.6.0). With the action, the GitHub runner can assume an IAM role as an IAM user (with access key), or using a web identity.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For a GitHub runner to have a web identity thereby assume an IAM role, we should &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws"&gt;configure OIDC provider&lt;/a&gt; in AWS. We can do that from AWS console (i.e. under IAM), or using CloudFormation code. Below is a snippet as an example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;GitHubOIDC&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AWS&lt;/span&gt;&lt;span style="color:#f92672"&gt;::&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;IAM&lt;/span&gt;&lt;span style="color:#f92672"&gt;::&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;OIDCProvider&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Properties&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Url&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//token.actions.githubusercontent.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ClientIdList&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sts&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;amazonaws&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;com&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ThumbprintList&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;6938&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;fd4d98bab03faadb97b34396831e3780aea1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then from the configured OIDC provider, we can &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html"&gt;obtain a thumbprint&lt;/a&gt;. GitHub action gives the thumbprint &lt;a href="https://github.blog/changelog/2022-01-13-github-actions-update-on-oidc-based-deployments-to-aws/"&gt;here&lt;/a&gt;. In AWS, we configure an IAM role whose AssumeRolePolicyDocument will reference the thumbprint. &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws"&gt;Here&lt;/a&gt; is an example. In the condition section of AssumeRolePolicyDocument, we can also specify a specific GitHub repository so that only Actions from that repository can assume the IAM role with their web identities.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Configure&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AWS&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Credentials&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;uses&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;aws&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;configure&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;aws&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;credentials&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;@&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node16&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;role&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;to&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;assume&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$&lt;/span&gt;{{ &lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;IAM_ROLE_ARN&lt;/span&gt; }}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;aws&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;region&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$&lt;/span&gt;{{ &lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AWS_REGION&lt;/span&gt; }}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This way, we map a GitHub runner&amp;#8217;s web identity to an IAM role with a &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#updating-your-github-actions-workflow"&gt;step&lt;/a&gt; using the Action above. We also filter what GitHub org and what repo can trigger actions that assumes the role, by the condition clause in the role statement. If the step fails, we can look at CloudTrail on the AWS side for causes. Look for entries with &lt;em&gt;AssumeRoleWithWebIdentity&lt;/em&gt; as Event Name.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-reusable-workflows"&gt;Reusable workflows &lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For better reusability of Action steps, GitHub introduced reusable workflows (&lt;a href="https://github.blog/2021-11-29-github-actions-reusable-workflows-is-generally-available/"&gt;generally available&lt;/a&gt; since Nov 2021). It is particularly helpful when we need to run a workflow for different environments. The reusable workflows files (YML) can be placed in separate repositories, and be reference as such. This allows enterprises to centralize the management of reusable workflows.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I have been using the &lt;a href="https://github.com/nektos/act"&gt;act&lt;/a&gt; project to emulate GitHub action locally on MacBook. As of Jan 2023, act does &lt;a href="https://github.com/nektos/act/issues/826"&gt;not support&lt;/a&gt; reusable workflow. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With the split between caller and reusable workflows, we have a new challenge of passing secrets and variables between them. It is not straightforward and GitHub documentation needs improvement to get the documentation clear. Also because the word &amp;#8220;environment&amp;#8221; is used in different contexts, it is ambiguous and therefore difficult to Google relevant information.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-passing-variables"&gt;Passing variables&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;First, there are several types of &lt;a href="https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow"&gt;variables&lt;/a&gt; in GitHub action:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Environment variable&lt;/strong&gt;: declared under &lt;em&gt;env&lt;/em&gt; keyword in a workflow. To use environment variable, use the &lt;em&gt;env&lt;/em&gt; context. For example: ${{ env.MY_VARIABLE }}&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Configuration variable&lt;/strong&gt;: &lt;a href="https://github.blog/changelog/2023-01-10-github-actions-support-for-configuration-variables-in-workflows/"&gt;introduced&lt;/a&gt; in Jan 2023, &lt;a href="https://docs.github.com/en/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows"&gt;configuration variables&lt;/a&gt; are defined at repository, environment and organization levels. To use configuration variable, use &lt;em&gt;vars&lt;/em&gt; context, and ensure the workflow job specifies a value for environment attribute. &lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Secrets&lt;/strong&gt;: GitHub also calls it &lt;a href="https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-secrets"&gt;Environment secret&lt;/a&gt; when defined at environment level. It works the same way as a configuration variable because it is also specific to an environment. The content is not viewable once set. &lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The reason GitHub action makes this so confusing, is that on &lt;a href="https://docs.github.com/en/actions/learn-github-actions/variables#about-variables"&gt;one&lt;/a&gt; page, its documentation distinguishes between environment variable and configuration variable:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="240" src="https://www.digihunch.com/wp-content/uploads/2023/01/gh1.webp" alt="" class="wp-image-12891" srcset="https://www.digihunch.com/wp-content/uploads/2023/01/gh1.webp 1024w, https://www.digihunch.com/wp-content/uploads/2023/01/gh1-300x70.webp 300w, https://www.digihunch.com/wp-content/uploads/2023/01/gh1-768x180.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On &lt;a href="https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-variables"&gt;another page,&lt;/a&gt; the document refers to configuration variables at environment level as environment variable:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="185" src="https://www.digihunch.com/wp-content/uploads/2023/01/gh2.png" alt="" class="wp-image-12892" srcset="https://www.digihunch.com/wp-content/uploads/2023/01/gh2.png 1024w, https://www.digihunch.com/wp-content/uploads/2023/01/gh2-300x54.png 300w, https://www.digihunch.com/wp-content/uploads/2023/01/gh2-768x139.png 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It seems that &amp;#8220;configuration variable&amp;#8221; is too new for GitHub to refine its documentation as of January. This semantical confusion gave me a hard time investigating how to pass &amp;#8220;Environment variable&amp;#8221; to reusable workflows. I will stick to the meaning on the first page to distinguish environment variable and configuration variable at environment level. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Passing environment variable isn&amp;#8217;t straightforward. In &lt;a href="https://github.com/orgs/community/discussions/26671"&gt;this&lt;/a&gt; discussion thread, people discussed how inconvenient it is. I used the workaround in &lt;a href="https://github.com/orgs/community/discussions/26671#discussioncomment-4295807"&gt;this&lt;/a&gt; comment, where I had to create a job for the sake of storing variable values to output. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Pass secret is easier. &lt;a href="https://colinsalmcorner.com/consuming-environment-secrets-in-reusable-workflows/"&gt;This&lt;/a&gt; is an insightful blog post (Dec 2021) about passing secret to reusable workflow. The attempt 3 in the post works for me. First, we pass the value of environment to the reusable workflow as an input, then at job level specify the environment with the value. Then in the jobs we can reference secrets as ${{ secrets.NAME }}. The job will pick up the secret based on the correct environment. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It appears that since May 2022, GitHub &lt;a href="https://github.blog/changelog/2022-05-03-github-actions-simplify-using-secrets-with-reusable-workflows/"&gt;introduced&lt;/a&gt; &lt;em&gt;&lt;code&gt;secrets: inherit&lt;/code&gt;&lt;/em&gt; keyword to address this. However, the method above still works for configuration variable.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-authentication-of-github-actions"&gt;Authentication of GitHub Actions &lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;By default, a GitHub action can access the code repository that triggers the action and no other repositories (with &lt;a href="https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret"&gt;GITHUB_TOKEN&lt;/a&gt;). However, in many cases we need to access external repositories. For example, &lt;code&gt;terraform init&lt;/code&gt; command from a GitHub action implicitly calls &lt;code&gt;git clone&lt;/code&gt; to pull module code from external repositories. A GitHub workflow may also reference a workflow file from external repositories.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The question is how to authenticate GitHub workflow to access external repo. This &lt;a href="https://michaelheap.com/ultimate-guide-github-actions-authentication/"&gt;post&lt;/a&gt; has a thorough discussion. We may create a &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token"&gt;Personal Access Token&lt;/a&gt; and pass it to &lt;a href="https://github.com/marketplace/actions/setup-git-credentials"&gt;set-git-credentials&lt;/a&gt; action. We are essentially sharing a personal credential (and repo access) with a GitHub action, which is not a good practice. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The proper way to solve this problem, is to create a separate GitHub App and grant the access only the repo that the workflow needs to access. The GitHub App will generate a private key. Then we supply the private key to &lt;a href="https://github.com/marketplace/actions/workflow-application-token-action"&gt;workflow-application-token-action&lt;/a&gt; so the workflow can act as the GitHub App, thereby access the external repos. The post has more &lt;a href="https://michaelheap.com/ultimate-guide-github-actions-authentication/#github-apps"&gt;details&lt;/a&gt; in the GitHub App section. Suppose we have &lt;code&gt;terraform get&lt;/code&gt; command to clone external repo, the actions may look like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HashiCorp&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Setup&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Terraform&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;uses&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hashicorp&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;setup&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;terraform&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;@&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RepoReader&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;App&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Token&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_repo_reader_token&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;uses&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;peter&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;murray&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;workflow&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;application&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;token&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;action&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;@&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;application_id&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$&lt;/span&gt;{{ &lt;span style="color:#a6e22e"&gt;vars&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;REPO_READER_APPLICATION_ID&lt;/span&gt; }}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;application_private_key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$&lt;/span&gt;{{ &lt;span style="color:#a6e22e"&gt;secrets&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;REPO_READER_PRIVATE_KEY&lt;/span&gt; }}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Cache&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Git&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Creds&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;uses&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;de&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;vri&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;es&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;setup&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;git&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;credentials&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;@&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;credentials&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//x-access-token:${{ steps.get_repo_reader_token.outputs.token }}@github.com/&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Terraform&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;implicitly&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;calls&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;git&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;clone&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;which&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;uses&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;credential&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cached&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;above&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Terraform&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;run&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;terraform&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Another benefit of using GitHub App is that the token is a short-lived credential that expires as the job is finished, whereas a PAT will expire on a preset date. In this use case we can think of GitHub App as a service account with minimized privilege to read a short list of repos.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-final-words"&gt;Final words&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I came across &lt;a href="https://www.reddit.com/r/devops/comments/10t0xqj/unpopular_opinion_cicd_engines_are_an_awful_idea/"&gt;this&lt;/a&gt; reddit post against pipeline use and there are some legit points. For example, the user is frustrated with the limitation with variable passing and unclear documentation. The post wasn&amp;#8217;t specific to GitHub action but I do share some frustration while working with GitHub Actions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On a positive note, since I first used GitHub actions, it has evolved quite a bit with new features, although the documentation is somewhat lagging. It is still very helpful as all of these are free to personal use. I look forward to more interesting features. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2023/01/optimize-cpu-and-memory-for-kubernetes-pods/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Optimize CPU and Memory for Kubernetes Pod&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2023/02/dicom-testing-with-tls/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;DICOM testing over TLS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Getting started with GitHub Actions</title><link>https://www.digihunch.com/2021/05/getting-started-with-github-actions/</link><pubDate>Thu, 27 May 2021 13:52:31 -0400</pubDate><guid>https://www.digihunch.com/2021/05/getting-started-with-github-actions/</guid><description>&lt;p class="wp-block-paragraph"&gt;In my &lt;a class="rank-math-link" href="https://github.com/digihunch/orthweb"&gt;orthweb&lt;/a&gt; &lt;a href="https://www.digihunch.com/projects/"&gt;project&lt;/a&gt;, I had to compile a library on my own. In search for free computing resources I realized that GitHub action can meet all my needs.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-ci-cd-pipeline"&gt;CI/CD pipeline&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As a development project grows, there are many operational tasks demanding automation. Prior to pipeline technology, developers used to use &lt;a href="https://en.wikipedia.org/wiki/Make_(software)"&gt;Makefile&lt;/a&gt; to organize command execution locally. Today, its role has declined, but &lt;a href="https://tech.trivago.com/post/2019-12-20-makefiles-in-2019/"&gt;Makefile&lt;/a&gt; is a good choice in certain situations. In most cases though, to offload the build command execution to a shared system, automation engines like Jenkins came around. Then Jenkins evolved into pipelines.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In strict terms, CI pipeline is the build pipeline; and CD pipeline is release pipeline. The two types of pipelines use pretty much the same pool of building blocks, with different purposes. The build pipeline focuses on producing quality artifact in a consistent manner. The release pipelines focus on system stability while deploying an artifact across different environments. Because release pipelines may connect to different environment, it has to deal with various situations. It is very common to have multiple stages in release pipeline, each stage pointing to a different environment (e.g. DEV, TEST and PROD). At workplace both could be loosely referred to as CI/CD pipeline, or even simply pipeline.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A lot of projects provide pipeline capability: BitBucket, Bamboo, TeamCity, Jenkins, Azure DevOps, AWS CodePipeline, TravisCI etc. Since late 2018, GitHub also joined the game with GitHub actions. It is openly &lt;a class="rank-math-link" href="https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#:~:text=GitHub%20Actions%20usage%20is%20free,is%20controlled%20by%20spending%20limits."&gt;free&lt;/a&gt; for public repositories, and has a free tier for private repositories. It executes task as defined in .github/workflow/action.yaml in the code project. I will take my own project as an example.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-runners"&gt;Runners&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can run jobs in self-hosted runners or GitHub managed runners, similar to other pipeline solutions (e.g. self-hosted agent vs managed agent from Azure DevOps). The &lt;a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#github-hosted-runners" class="rank-math-link"&gt;GitHub hosted runners&lt;/a&gt; only have three operating systems to support: Windows, Ubuntu and MacOS. The Ubuntu and Windows runners are built from Standard_DS2_v2 VMs in Microsoft Azure. They are pre-installed with a &lt;a href="https://github.com/actions/virtual-environments" class="rank-math-link"&gt;virtual environment &lt;/a&gt;with packages required for common build tasks. The same virtual environment is also used in hosted agents by Azure DevOps. While they are free and you can elevate privilege on the runner, you cannot SSH or RDP to it for further troubleshooting. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners" class="rank-math-link"&gt;self-hosted runners&lt;/a&gt; require users to manage the instance on their own, including configuring virtual environment, installing &lt;a href="https://github.com/actions/runner" class="rank-math-link"&gt;GitHub Action Runner&lt;/a&gt;, etc.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-workflow-file"&gt;Workflow file&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Most pipeline declaration uses YAML or JSON, such as &lt;a href="https://www.jenkins.io/doc/book/pipeline/jenkinsfile/" class="rank-math-link"&gt;Jenkinsfile&lt;/a&gt;, AWS &lt;a href="https://docs.aws.amazon.com/code-samples/latest/catalog/code-catalog-cloudformation-codepipeline.html" class="rank-math-link"&gt;CodePipeline&lt;/a&gt;. GitHub refers to an automation process as a &amp;#8220;workflow&amp;#8221; and you can program the workflow in YAML (.github/workflow/action.yaml). Here is the &lt;a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions" class="rank-math-link"&gt;reference&lt;/a&gt; and an example with environmental variable and versioning: &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;script src="https://gist.github.com/digihunch/e6ed668872c5b0506d25f638ff70727e.js"&gt;&lt;/script&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The handling of environment is documented &lt;a href="https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable" class="rank-math-link"&gt;here&lt;/a&gt;. There are a lot of custom actions available in GitHub &lt;a href="https://github.com/marketplace" class="rank-math-link"&gt;Marketplace&lt;/a&gt;. For example, the versioning in the above example, uses an &lt;a href="https://github.com/marketplace/actions/nuget-build-number-generator" class="rank-math-link"&gt;action&lt;/a&gt; by &lt;a href="https://einaregilsson.com/a-github-action-for-generating-sequential-build-numbers/" class="rank-math-link"&gt;Einar Egilsson&lt;/a&gt;, which is open source itself.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-example-pipeline"&gt;Example pipeline&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;My example &lt;a href="https://github.com/digihunch/orthweb/blob/67542d9329be36e3b8ca895c8b71805c9711aaa3/.github/workflows/action.yml" class="rank-math-link"&gt;pipeline&lt;/a&gt; consists of two phases: &lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Build Library: spin up a docker container to build source code, and publish the artifact&lt;/li&gt;&#10;&lt;li&gt;Publish Image: add the artifact to an existing Docker image, and publish the result as my own image.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The status of the pipeline is also open, and can be found &lt;a href="https://github.com/digihunch/orthweb/actions" class="rank-math-link"&gt;here&lt;/a&gt;. The retention period of artifact is 90 days by default but can be &lt;a href="https://docs.github.com/en/actions/reference/usage-limits-billing-and-administration#artifact-and-log-retention-policy" class="rank-math-link"&gt;customized&lt;/a&gt;. To persist the artifact, I add it to my own Docker image and publish it to &lt;a href="https://hub.docker.com/r/digihunch/orthanc-plugin" class="rank-math-link"&gt;DockerHub&lt;/a&gt;, hence the second phase.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1152" height="724" src="https://www.digihunch.com/wp-content/uploads/2021/05/image-1.png" alt="" class="wp-image-2299"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt; When building the second phase, I need to generate secret from my DockerHub account and store that &lt;a href="https://docs.github.com/en/actions/reference/encrypted-secrets" class="rank-math-link"&gt;encrypted secrets&lt;/a&gt; in GitHub settings, so that the secret value can be referenced in workflow file.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Failures in Actions are displayed in error steps and by default the rest of the steps are skipped. &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="968" height="624" src="https://www.digihunch.com/wp-content/uploads/2021/05/image-2.png" alt="" class="wp-image-2301"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use the &lt;strong&gt;&lt;a class="rank-math-link" href="https://github.com/marketplace/actions/docker-build-push-action"&gt;Docker build &amp;amp; push plugin&lt;/a&gt;&lt;/strong&gt; to build and push my own docker image to DockerHub. Apart from DockerHub as my choice, GitHub also has its own artifactory GitHub &lt;a class="rank-math-link" href="https://github.com/features/packages"&gt;Packages&lt;/a&gt; with a small free tier. It supports NPM, Docker, Maven, Gradle, etc. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Triggers of Action&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Most of the times, GitHub action are triggered upon commit to main branch of the repo. In GitHub, this is known as a &lt;a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch"&gt;workflow_dispatch&lt;/a&gt; event. This is not the only event that can trigger GitHub action. All the available events are listed &lt;a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#available-events"&gt;here&lt;/a&gt; on its documentation. This makes it very flexible to trigger action at many points in the workflow. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One example is to trigger GitHub action during PR review. When a developer opens a PR with a few commits in the proposed branch, the PR can preemptively check linting, style, etc and even build the application. These activities can also be defined in a GitHub action manifest with &lt;strong&gt;pull_request&lt;/strong&gt; as triggering event.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Troubleshooting&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In general, it is painful to troubleshoot activities happening inside of runners. I often had to write a few steps for the sake of printing variables, and trigger a run to see what their value is. This requires a lot of time especially when I have to wait for available runners. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To help troubleshooting pipeline runs there is an open-source utility called &lt;a href="https://github.com/nektos/act"&gt;act&lt;/a&gt;. You can run GitHub actions locally from a Docker container on your MacBook. You can deliver environment variables and secrets via files. If you ever need to troubleshoot the runner environment, you have the option to connect to the Shell environment inside of the runner container. This tool is extremely helpful.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-closing-remarks"&gt;Closing remarks&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;GitHub action really makes the CI/CD pipeline capability available to any developers who stores their code on GitHub. GitHub expands from a code repository solution to a full CI/CD solution with a free tier sufficient for personal projects.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2021/05/secure-web-application-deployment/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Secure web application deployment&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2021/06/kubernetes-storage-explained/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Storage Explained – from in-tree plugin to CSI&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Git Branching Strategy</title><link>https://www.digihunch.com/2021/03/git-branching-strategy/</link><pubDate>Sun, 07 Mar 2021 19:20:42 -0400</pubDate><guid>https://www.digihunch.com/2021/03/git-branching-strategy/</guid><description>&lt;p class="wp-block-paragraph"&gt;I have been in two discussions about &lt;a href="https://www.digihunch.com/2019/06/git-explained-1-of-2/"&gt;Git&lt;/a&gt; branching strategy in different organizations. Too many concepts! So I open this post to jot down the lineage of common branching strategies to help organizations develop their branching policies. In terms of reference, there is a lot from Atlassian documentation. In addition, I also find this one &lt;a class="rank-math-link" href="https://medium.com/factualopinions/branching-models-in-a-nutshell-bf24ea1d888a"&gt;article &lt;/a&gt;a good resource.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-centralized-workflow-no-branching"&gt;Centralized workflow (no branching)&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In &lt;a href="https://www.atlassian.com/git/tutorials/comparing-workflows#centralized-workflow" class="rank-math-link"&gt;Centralized Workflow&lt;/a&gt;, the team uses a central repository to serve as the single-point-of-entry for all changes to the project. The default branch is master, and all changes are committed to this branch. This workflow does not require any other branches beside master. Local changes may conflict with upstream commits, and conflict needs to be resolved.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This workflow is usually seen in teams transitioning from SVN, with very basic skill level. This workflow may also be adopted in teams working on configuration management instead of source code. Centralized workflow is great for small teams. The conflict resolution process can form a bottleneck as the team scales in size.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-feature-branch-workflow"&gt;Feature Branch Workflow &lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Instead of directly committing on their local master branch, developers create a new branch every time they start work on a new feature. Feature branches should have descriptive names (e.g. issue#112). Feature branches are pushed to the central repository so that they can be shared to other developers without touching any official (master) code. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To get feedback on the new feature branch, create a pull request in a repository management solution (e.g. Bitbucket Cloud, Bitbucket Server). Before merge, you may have to resolve merge conflicts if others have made changes to the master branch of repo. This is to make sure your local master is synchronized with the upstream master. When your pull request is approved and conflict free, you can merge your branch to master branch.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter is-resized"&gt;&lt;img decoding="async" src="https://wac-cdn.atlassian.com/dam/jcr:09308632-38a3-4637-bba2-af2110629d56/07.svg?cdnVersion=1491" alt="Feature Branch Workflow: Merging a feature branch" style="width:549px;height:97px"/&gt;&lt;figcaption class="wp-element-caption"&gt;feature branch workflow&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Git Feature Branch Workflow is a composable workflow that can be leveraged by other high-level Git workflows. Git Feature Branch Workflow is branching model focused, instead of release focused. The Git Feature Branch Workflow can be incorporated into other workflows. The Gitflow, and Git Forking Workflows traditionally use a Git Feature Branch Workflow in regards to their branching models.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-gitflow-workflow"&gt;Gitflow Workflow&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;First &lt;a href="https://nvie.com/posts/a-successful-git-branching-model/"&gt;published&lt;/a&gt; in 2010 by Vincent Driessen. Gitflow defines a strict branching model designed around the project release. This provides a robust framework for managing larger projects. In addition to Feature Branch Workflow, Gitflow workflow assigns very specific roles to different branches and defines how and when they should interact.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Instead of a single master branch, this workflow uses two branches to record the history of the project. The master branch stores the official release history, and the develop branch serves as an integration branch for features. It is also convenient to tag all commits in the master branch with a version number.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This workflow is operated in the following ways:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;A develop branch is created from master&lt;/li&gt;&#10;&lt;li&gt;Feature branches are created from develop. When a feature is complete, with PR reviewed, it is merged into the develop branch. Features branches&lt;/li&gt;&#10;&lt;li&gt;Once develop has acquired enough features for a release (or a predetermined release date is approaching), we fork a release branch off of develop. Creating this branch starts the next release cycle, so new features can be added to develop after this point. On the release branch itself, only bug fixes, documentation generation, and other release-oriented tasks should go in this branch. Once ready to ship, the release branch gets merged into master and tagged with a version number. In addition, it should also be merged back into develop, which may have progressed since the release was initiated.&lt;/li&gt;&#10;&lt;li&gt;Maintenance or hotfix branches are used to quicly patch production releases. Hotfix branches are a lot like release branchs and feature branches except they&amp;#8217;re based on master instead of develop. As soon as the fix is complete, it should be merged into both master and develop (or the current release branch), and master should be tagged with an updated version number. &lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter is-resized"&gt;&lt;img decoding="async" src="https://wac-cdn.atlassian.com/dam/jcr:61ccc620-5249-4338-be66-94d563f2843c/05%20(2).svg?cdnVersion=1491" alt="Git flow workflow - Hotfix Branches" style="width:572px;height:409px"/&gt;&lt;figcaption class="wp-element-caption"&gt;Gitflow workflow&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There is a Git extension named git-flow to provide high-level repository operations for this Workflow, such as start a release, finish a release, start a hotfix, finish a hotfix.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Gitflow is ideally suited for projects that have a scheduled release cycle and for the DevOps best practice of continuous delivery. It ensures that the master branch reflects what is deployed (e.g. in production). However, it is quite &lt;strong&gt;complex&lt;/strong&gt; and have a &lt;strong&gt;steep learning curve&lt;/strong&gt; for organizations. It also runs &lt;strong&gt;long-lived branches&lt;/strong&gt;, which is considered bad from CI/CD perspective. Branches are by definition to isolate and hide changes, whereas &lt;strong&gt;continuous integration &lt;/strong&gt;is about exposing changes early on and frequently. In that sense, the Gitflow branching model and CI/CD are &lt;strong&gt;mutually exclusive&lt;/strong&gt; ideas. &lt;/p&gt;&#10;&lt;blockquote class="wp-block-quote is-style-plain is-layout-flow wp-block-quote-is-layout-flow"&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In 2020, Vincent Driessen added a note at the beginning of his &lt;a href="https://nvie.com/posts/a-successful-git-branching-model/"&gt;article&lt;/a&gt; on Gitflow:&lt;/p&gt;&#10;&lt;cite&gt;If your team is doing continuous delivery of software, I would suggest to adopt a much simpler workflow (like GitHub flow) instead of trying to shoehorn git-flow into your team.&lt;br&gt;If, however, you are building software that is explicitly versioned, or if you need to support multiple versions of your software in the wild, then git-flow may still be as good of a fit to your team as it has been to people in the last 10 years.&lt;/cite&gt;&lt;/blockquote&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Driessen&amp;#8217;s notes also points to some simple alternatives. On the other hand, the &lt;a href="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow"&gt;Atlassian tutorial&lt;/a&gt; on Gitflow has described Gitflow workflow as a legacy (since Aug 2021 based on web &lt;a href="https://web.archive.org/web/20210802194504/https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow"&gt;archive&lt;/a&gt;). It points out at the beginning that Gitflow has fallen in popularity in favor of&amp;nbsp;&lt;a href="https://www.atlassian.com/continuous-delivery/continuous-integration/trunk-based-development"&gt;trunk-based workflows&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-github-flow-trunk-based-development-tbd-and-gitlab-flow"&gt;GitHub flow, Trunk-based development (TBD) and GitLab flow&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is operationally expensive to manage multiple mainlines in Git flow workflow, with both source control and release in the picture. Some simple alternatives have been evolved, with single mainline, for example, &lt;strong&gt;GitHub flow&lt;/strong&gt; and &lt;strong&gt;trunk-based development&lt;/strong&gt;. They differ in where the release is performed from. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &lt;a href="https://guides.github.com/introduction/flow/" class="rank-math-link"&gt;GitHub flow&lt;/a&gt;, release is performed from branch before being merged back to master (trunk).&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://trunkbaseddevelopment.com/alternative-branching-models/githubflow1.png" alt=""/&gt;&lt;figcaption class="wp-element-caption"&gt;GitHub flow&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In &lt;a href="https://www.atlassian.com/continuous-delivery/continuous-integration/trunk-based-development" class="rank-math-link"&gt;trunk-based development&lt;/a&gt;, release is not performed until the feature branch has been merged to the trunk (master). In trunk-based development, feature branches are supposed to be short-lived. It is a common practice among DevOps teams, since it streamlines merging and integration phases.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://trunkbaseddevelopment.com/5-min-overview/trunk_pr.png" alt=""/&gt;&lt;figcaption class="wp-element-caption"&gt;Trunk-based development&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Trunk-based development has gained some momentum in recent years, due to its DevOps friendliness. &lt;a href="https://trunkbaseddevelopment.com/" class="rank-math-link"&gt;This &lt;/a&gt;is a website that advocates it and here&amp;#8217;s a DZone &lt;a href="https://dzone.com/articles/why-i-prefer-trunk-based-development-over-feature" class="rank-math-link"&gt;article &lt;/a&gt;about it.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-gitlab-flow"&gt;Gitlab Flow&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In response to the shortcomings of GitHub flow and Gitflow, Gitlab introduced its own proposal of branching strategy, known as &lt;a href="https://docs.gitlab.com/ee/topics/gitlab_flow.html" class="rank-math-link"&gt;Gitlab flow&lt;/a&gt;. The most distinctive aspect is the environment branches. In Gitlab flow, you run multiple long-lived branches, each of them representing an environment. The typical steps are as follows:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;You create short-lived feature branches, and merge them often to the master.&lt;/li&gt;&#10;&lt;li&gt;Every developer starts from master and targets master. Other branches are merged from previous lower environment branches.&lt;/li&gt;&#10;&lt;li&gt;You can deploy a new version to production, by merging master into the production branch.&lt;/li&gt;&#10;&lt;li&gt;If you need to know what code is in production, you can check out the production branch to see.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="528" height="700" src="https://www.digihunch.com/wp-content/uploads/2023/01/gitlab_flow.png" alt="" class="wp-image-7742" srcset="https://www.digihunch.com/wp-content/uploads/2023/01/gitlab_flow.png 528w, https://www.digihunch.com/wp-content/uploads/2023/01/gitlab_flow-226x300.png 226w" sizes="auto, (max-width: 528px) 100vw, 528px" /&gt;&lt;figcaption class="wp-element-caption"&gt;&lt;strong&gt;Gitlab flow&lt;/strong&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You only need to work with release branches, if you need to release software code to the outside world. Here are some &lt;a class="rank-math-link" href="https://about.gitlab.com/topics/version-control/what-are-gitlab-flow-best-practices/"&gt;best practices&lt;/a&gt; in GitLab flow.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-forking-workflow"&gt;Forking Workflow&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Forking workflow is fundamentally different. The key steps are as follows:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;A developer &amp;#8216;forks&amp;#8217; an &amp;#8216;official&amp;#8217; server-side repository. This creates their own server-side copy. This is their personal public repository, and no other developer are allowed to push to it.&lt;/li&gt;&#10;&lt;li&gt;The new server-side copy is cloned to their local system. This forms an environment dedicated to this developer.&lt;/li&gt;&#10;&lt;li&gt;With the local clone, developer needs to create the upstream remote manually using &amp;#8220;git remote add upstream&amp;#8221; command. This allows the developer keep the local repository up-to-date as the official project progresses. &lt;/li&gt;&#10;&lt;li&gt;A new local feature branch is created. Developer commits to the new local branch, and pushes to their own copy of repository on server.&lt;/li&gt;&#10;&lt;li&gt;Developer files a pull request from the new branch (in own copy of repository) to the &amp;#8216;official&amp;#8217; repository. The project maintainer knows that an update is ready to be integrated. The PR also serves as a discussion thread.&lt;/li&gt;&#10;&lt;li&gt;The PR gets approved for merge and is merged into the original server-side repository.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This workflow has other names, such as &lt;a href="https://blog.scottlowe.org/2015/01/27/using-fork-branch-git-workflow/" class="rank-math-link"&gt;fork-and-branch workflow&lt;/a&gt;, and is commonly used in GitHub for managing open-source projects. However, this should not be confused with the aforementioned GitHub flow.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-conclusion"&gt;Conclusion&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In order to fully support distributed source control, Git abstract version control problems into concepts such as commit, branch, etc. This makes discussion about Git workflow and branching strategy difficult due to the conceptual hurdles and organization differences. We covered choices of Git branching strategy in this post. As Vincent Driessen commented in his &lt;a class="rank-math-link" href="https://nvie.com/posts/a-successful-git-branching-model/"&gt;original Gitflow posting&lt;/a&gt;, panaceas don&amp;#8217;t exist. We should consider the context (e.g. team size, Git skill level, etc) to determine the best Git branching strategy.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2021/02/interpret-census-data-from-statistics-canada/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Census Data from Statistics Canada&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2021/03/intro-to-data-analytics-platform/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to Data Analytics Platform on Azure&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Automatic deployment of Orthanc on AWS</title><link>https://www.digihunch.com/2020/11/medical-imaging-web-server-deployment-pipeline/</link><pubDate>Sun, 08 Nov 2020 00:54:06 -0400</pubDate><guid>https://www.digihunch.com/2020/11/medical-imaging-web-server-deployment-pipeline/</guid><description>&lt;p class="wp-block-paragraph"&gt;[&lt;strong&gt;Update&lt;/strong&gt;] I changed reverse proxy from Nginx to Envoy. &lt;a href="https://www.digihunch.com/2022/03/from-nginx-to-envoy-proxy/"&gt;Here&lt;/a&gt;&amp;#8216;s the detail.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;[&lt;strong&gt;Update&lt;/strong&gt;] Some security improvement was introduced in may 2021. &lt;a class="rank-math-link" href="https://www.digihunch.com/2021/05/secure-web-application-deployment/"&gt;Here&lt;/a&gt;&amp;#8216;s detail.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;[&lt;strong&gt;Update&lt;/strong&gt;] &lt;a href="https://github.com/digihunch/orthweb"&gt;Here&amp;#8217;s&lt;/a&gt; the link to the orthweb repository.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this project we introduce a medical imaging web service based on Orthanc, an open-source project of DICOM server, and a pipeline to deploy such server automatically and consistently. We deploy Orthanc on AWS automatically. This little project involves a number of technical deets in DevOps, to deliver a web application prototype with an automated deployment pipeline.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-a-brief-on-imaging"&gt;A brief on imaging&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In medical imaging, scanning devices are the data collectors. It consists of various categories of scanners, such as Computed Tomography (CT), and Ultrasound (US). They are collectively referred to as modality, but vary significantly in terms of image generation and hardware manufacturing. The challenges to exchange data between these heterogeneous scanning devices and centralized computers came around as early as the 1980s, which brought about ACR-NEMA standard in 1985, under the initiative between American College Radiology (ACR) and National Electrical Manufacturers Association (NEMA). The standard lately evolved into DICOM (Digital Imaging Communication in Medicine), a comprehensive set of standard in the ISO framework that governs modern imaging data storage and exchange across several disciplines (radiology, cardiology, pathology, etc) that operate around images in medicine.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In addition to defining a &lt;a href="http://dicom.nema.org/medical/dicom/current/output/chtml/part10/"&gt;file format&lt;/a&gt; to store imaging data, DICOM also includes an upper layer protocol that dictates how two compliant devices (referred as application entity, each identified by AE title) can negotiate a common syntax to transfer objects (e.g. an image, a report or a discovery). Upper layer refers to layer 5-7 in OSI model, or application layer in TCP/IP model.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-imaging-server"&gt;Imaging server&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Once scanner acquires images from patient, they stores the exams to imaging server for persistent storage. The functionalities of such server expands overtime since 1990s and hence go by different names in different eras, such as PACS (Picture Archive and Communication Systems), VNA (Vendor Neutral Archive) and EI (enterprise imaging) archive. Regardless of naming, they can be generally seen as a highly specialized variation of enterprise content management system. They are usually hosted with a centralized database to index clinical information at patient, exam and image levels. The other key component is the persistent storage devices, usually in the form of a &lt;a href="https://en.wikipedia.org/wiki/Network-attached_storage" class="rank-math-link"&gt;NAS&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://www.orthanc-server.com/" class="rank-math-link"&gt;Orthanc&lt;/a&gt; is an open-source initiative for such imaging servers. It provides a DICOM endpoint, allowing scanning devices to store medical images. It also provides a web viewer allowing users to see the images stored. It is released for many platforms, including Docker images.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-infrastructure-as-code"&gt;Infrastructure as code&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use Amazon Web Service (AWS) for infrastructure as service, and &lt;a href="https://www.terraform.io/" class="rank-math-link"&gt;Terraform&lt;/a&gt; as the tool to provision resources off AWS, in a reliable and consistent mechanism, known as Infrastructure-as-Code. Terraform is an alternative to CloudFormation, AWS&amp;#8217;s proprietary infrastructure-as-code technology. Terraform is developed by Hashicorp as an open-source project, and therefore is vendor neutral. It supports multiple public cloud vendor through different &lt;a href="https://www.terraform.io/docs/providers/index.html" class="rank-math-link"&gt;providers&lt;/a&gt;. Each provider accesses the vendor specific SDK. For example, the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs" class="rank-math-link"&gt;AWS provider&lt;/a&gt; integrates with &lt;a href="https://aws.amazon.com/tools/" class="rank-math-link"&gt;AWS SDK&lt;/a&gt;. As a result, the code used in one vendor cannot just be applied to a different vendor without a major overhaul. Terraform&amp;#8217;s current version is 0.13 as of Oct 2020, and has gone through some &lt;a href="https://www.hashicorp.com/blog/announcing-terraform-0-12" class="rank-math-link"&gt;syntax changes&lt;/a&gt; since version 0.11. Terraform also produces files for state management locally in the working directory. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When executing, Terraform combines all files in the working directory to assess variables, and create required resources. It is compatible with the most of AWS resources. For example, you can specify user data with templates when creating EC2 instances. You can also create managed service instance as long as it is supported by the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs" class="rank-math-link"&gt;provider&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-architecture"&gt;Architecture&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Orthanc web server stores data in sqlite by default, but also has a plugin to support &lt;a href="https://wiki.postgresql.org/wiki/Main_Page" class="rank-math-link"&gt;PostgreSQL&lt;/a&gt;, an open-source relational database. AWS has managed service (&lt;a href="https://aws.amazon.com/rds/postgresql/" class="rank-math-link"&gt;RDS&lt;/a&gt;) based on PostgreSQL. In this project, we create an RDS instance that span across two availability zones for minimum high availability. Orthanc also supports storing imaging data including pixels in PostgreSQL, which obviates the need for a dedicated file storage system.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We deploy the application in Docker&amp;#8217;s containers for compatibility and portability. The Orthanc server is shipped in &lt;a class="rank-math-link" href="https://orthanc.uclouvain.be/book/users/docker.html"&gt;Docker images&lt;/a&gt;, available in &lt;a class="rank-math-link" href="https://hub.docker.com/r/jodogne/orthanc"&gt;Docker hub&lt;/a&gt; registry. The docker environment is configured as part of EC2 instance bootstrapping, including installing packages with &lt;a href="https://www.digihunch.com/2019/02/package-repository-management-in-linux/"&gt;YUM&lt;/a&gt;, initializing and customizing environment variables. The docker-compose file, and the auxiliary configuration files are provided in the repo. The bootstrapping script installs git and pulls required files from this &lt;a class="rank-math-link" href="https://github.com/digihunch/orthweb"&gt;GitHub repo&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This demo project does not include load balancing, DNS management, or container orchestration.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-security"&gt;Security&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Orthanc&amp;#8217;s web browser &lt;a href="https://orthanc.uclouvain.be/book/faq/https.html"&gt;natively supports HTTPS&lt;/a&gt;. However, the DICOM port does not support TLS natively, as their development has made clear in the &lt;a href="https://orthanc.uclouvain.be/book/faq/security.html" class="rank-math-link"&gt;FAQ&lt;/a&gt;. This leaves a severe security vulnerability because all patient data (protected health information in HIPPA context) would be sent across the Internet in the clear, visible to every network interface along the route. To address this issue we brought in Nginx as a reverse proxy to work at TCP layer to terminate encrypted traffic for Orthanc&amp;#8217;s DICOM end point. DICOM upper layer works on top of TCP layer. &lt;/p&gt;&#10;&lt;p class="has-pale-cyan-blue-background-color has-background wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="621px" viewBox="-0.5 -0.5 621 207" style="max-width:100%;max-height:207px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;path d="M 187.5 56 C 157.5 56 150 106 174 116 C 150 138 177 186 196.5 166 C 210 206 255 206 270 166 C 300 166 300 126 281.25 106 C 300 66 270 26 243.75 46 C 225 16 195 16 187.5 56 Z" fill="#fff2cc" stroke="#d6b656" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 203px; margin-left: 151px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Internet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="225" y="203" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Internet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 120 98.5 L 323.64 86.38" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 328.88 86.07 L 322.1 89.98 L 323.64 86.38 L 321.69 82.99 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 95px; margin-left: 220px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; "&gt;Encrypted DICOM traffic&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="220" y="98" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;Encrypted DICOM traffic&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="540" y="56" width="80" height="120" rx="12" ry="12" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 78px; height: 1px; padding-top: 116px; margin-left: 541px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;DICOM &lt;br&gt;Archive&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="580" y="120" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;DICOM&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="330" y="56" width="50" height="120" rx="7.5" ry="7.5" fill="#d5e8d4" stroke="#82b366" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 48px; height: 1px; padding-top: 116px; margin-left: 331px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Nginx&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="355" y="120" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Nginx&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 380 86 L 533.63 86" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 538.88 86 L 531.88 89.5 L 533.63 86 L 531.88 82.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 87px; margin-left: 456px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; "&gt;Unencrypted DICOM traffic&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="456" y="90" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;Unencrypted DICOM traffic&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="86" width="120" height="50" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 111px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;DICOM Device supporting TLS&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="115" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;DICOM Device support&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 540 146 L 386.37 146" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 381.12 146 L 388.12 142.5 L 386.37 146 L 388.12 149.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 146px; margin-left: 459px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; "&gt;Unencrypted DICOM traffic&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="459" y="149" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;Unencrypted DICOM traffic&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 330 146 L 126.33 124.18" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 121.11 123.62 L 128.44 120.88 L 126.33 124.18 L 127.7 127.84 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 125px; margin-left: 214px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; "&gt;Encrypted DICOM traffic&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="214" y="129" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;Encrypted DICOM traffic&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 312 6 L 312 206 M 308 206 L 308 6 M 308 206" fill="none" stroke="#6c8ebf" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 7px; margin-left: 321px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; "&gt;corporate firewall&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="321" y="10" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;corporate firewall&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 142 6 L 142 206 M 138 206 L 138 6 M 138 206" fill="none" stroke="#6c8ebf" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 7px; margin-left: 151px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; "&gt;corporate firewall&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="151" y="10" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;corporate firewall&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank" rel="noopener noreferrer"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Nginx literature, this use case is referred to as &lt;a class="rank-math-link" href="https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/"&gt;SSL Termination for TCP Upstream Servers&lt;/a&gt;. Note that Nginx is providing layer 4 capability in this use case so the certificate and key configuration should not be placed under http section of the configuration file. This layer 4 capability in fact enables security configurations of all protocol that operates in upper layers and can be used in a broad range of situations. It is also noteworthy that Nginx can re-encrypt the traffic on the way out to upstream, for even tighter security control measure as outlined in this &lt;a class="rank-math-link" href="https://docs.nginx.com/nginx/admin-guide/security-controls/securing-tcp-traffic-upstream/"&gt;use case&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;user nginx&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;worker_processes &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;error_log &lt;span style="color:#e6db74"&gt;/var/&lt;/span&gt;log&lt;span style="color:#e6db74"&gt;/nginx/&lt;/span&gt;error&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt; warn&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pid &lt;span style="color:#e6db74"&gt;/var/&lt;/span&gt;run&lt;span style="color:#e6db74"&gt;/nginx.pid;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;events {&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; worker_connections 1024;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;stream {&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; upstream dicom_backend {&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; server orthanc-backend:4242;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; server {&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; listen 11112 ssl;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; proxy_pass dicom_backend;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ssl_certificate conf.d/&lt;/span&gt;site&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pem&lt;/span&gt;&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_certificate_key conf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;d&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;site&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pem&lt;/span&gt;&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_protocols SSLv3 TLSv1 TLSv1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; TLSv1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_ciphers HIGH:&lt;span style="color:#f92672"&gt;!&lt;/span&gt;aNULL:&lt;span style="color:#f92672"&gt;!&lt;/span&gt;MD5:ECDH&lt;span style="color:#f92672"&gt;+&lt;/span&gt;AESGCM&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_session_cache shared:SSL:&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;m&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_session_timeout &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;h&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_handshake_timeout &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;s&lt;span style="color:#f92672"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;It is also helpful to use Nginx to terminate HTTPS traffic, using a pair of certificate and key. When testing with self-signed certificate I realized that Chrome browser has specific &lt;a href="https://support.apple.com/en-us/HT210176" class="rank-math-link"&gt;requirement&lt;/a&gt; on self-signed certificate, or it won&amp;#8217;t load the page. So the certificate has to be created as instructed &lt;a class="rank-math-link" href="https://eengstrom.github.io/musings/self-signed-tls-certs-v.-chrome-on-macos-catalina"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For better security, it is advisable that the RDS instance is provisioned in private subnet, with its data encrypted both in-transit and at-rest. Docker service should also manage sensitive information as &lt;a href="https://docs.docker.com/engine/swarm/secrets/" class="rank-math-link"&gt;secrets&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-summary"&gt;Summary&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The deliverable is stored in this Github &lt;a href="https://github.com/digihunch/orthweb" class="rank-math-link"&gt;repo&lt;/a&gt;. The docker part of it can be executed on MacBook with PostgreSQL. The entire hardware stack represented by terraform code, can be executed against AWS to create required resources. Checkout README for further instruction. To emulate a modality, one will need a TLS supported DICOM application entity, &lt;a href="https://horosproject.org/" class="rank-math-link"&gt;Horos&lt;/a&gt; is a great project on MacOS to serve this purpose, both as DICOM-compliant sender and a viewer. Alternatively, consider some command-line based DICOM toolkit such as &lt;a href="https://support.dcmtk.org/redmine/projects/dcmtk" class="rank-math-link"&gt;dcmtk&lt;/a&gt;, or &lt;a href="https://sourceforge.net/projects/gdcm/" class="rank-math-link"&gt;grassroot dicom&lt;/a&gt;.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/11/docker-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Docker storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/11/how-imaging-devices-talk-to-each-other-tip-in-dicom/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;How imaging devices talk to each other (in DICOM)&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Automated Deployment Pipeline 3 of 3</title><link>https://www.digihunch.com/2020/10/automated-deployment-pipeline-3-of-3/</link><pubDate>Wed, 14 Oct 2020 17:27:00 -0400</pubDate><guid>https://www.digihunch.com/2020/10/automated-deployment-pipeline-3-of-3/</guid><description>&lt;h3 class="wp-block-heading" id="h-background"&gt;Background&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We have previously covered a pipeline example with Jenkins calling Ansible to leverage OpenSSH configuration and Ansible inventory. We also discussed a use case with declarative pipeline.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this posting, I will provide another advanced example, built on declarative pipeline. The pipeline file will be pulled from Git repository. Also, the script is executed on a remote agent, instead of the Jenkins master server. The reason this example is important, is that:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Jenkinsfile is now version controlled (pipeline as code);&lt;/li&gt;&#10;&lt;li&gt;Service script (e.g. python) is also version controlled from a central repository;&lt;/li&gt;&#10;&lt;li&gt;Computing resource is provided by a remote agent. Since the script is pulled before running, the agent is still fungible;&lt;/li&gt;&#10;&lt;li&gt;The result from service script execution is archived (similar to the way build artifact is stored in Jenkins) in master.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The architecture of this pipeline now becomes the followings:&lt;/p&gt;&#10;&lt;p class="has-pale-cyan-blue-background-color has-background wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="591px" viewBox="-0.5 -0.5 591 671" style="max-width:100%;max-height:671px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="220" y="0" width="130" height="190" rx="19.5" ry="19.5" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 7px; margin-left: 221px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Vendor&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="19" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Vendor&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="430" y="390" width="160" height="280" rx="24" ry="24" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 397px; margin-left: 431px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Customer B&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="510" y="409" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer B&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="400" width="160" height="270" rx="24" ry="24" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 407px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Customer A&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="80" y="419" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer A&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="240" y="110" width="90" height="30" rx="4.5" ry="4.5" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 125px; margin-left: 241px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;SSH Proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="129" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;SSH Proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="240" y="30" width="90" height="60" rx="9" ry="9" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 60px; margin-left: 241px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Jenkins (Master)&lt;br&gt;Ansible&lt;br&gt;OpenSSH&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="64" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Jenkins (Master&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="285" cy="190" rx="30" ry="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 190px; margin-left: 256px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;VPN&lt;br&gt;Gateway&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="194" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;VPN&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="150" cy="410" rx="30" ry="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 410px; margin-left: 121px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;VPN&lt;br&gt;Gateway&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="414" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;VPN&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="430" cy="400" rx="30" ry="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 400px; margin-left: 401px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;VPN&lt;br&gt;Gateway&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="430" y="404" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;VPN&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 215 285 C 163 285 150 320 191.6 327 C 150 342.4 196.8 376 230.6 362 C 254 390 332 390 358 362 C 410 362 410 334 377.5 320 C 410 292 358 264 312.5 278 C 280 257 228 257 215 285 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 258px; height: 1px; padding-top: 320px; margin-left: 151px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Internet&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="280" y="324" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Internet&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="470" width="100" height="90" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 515px; margin-left: 31px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;agent.jar&lt;br&gt;Ansible&lt;br&gt;OpenSSH&lt;br&gt;Git&lt;br&gt;Python&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="80" y="519" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;agent.jar&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="460" y="470" width="100" height="90" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 515px; margin-left: 461px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;agent.jar&lt;br&gt;Ansible&lt;br&gt;OpenSSH&lt;br&gt;Git&lt;br&gt;Python&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="510" y="519" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;agent.jar&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 282.5 257.65 L 284.58 226.35" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 282.15 262.88 L 279.13 255.67 L 282.5 257.65 L 286.11 256.13 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 284.93 221.12 L 287.95 228.33 L 284.58 226.35 L 280.97 227.87 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 173.61 382.89 L 181.4 363.7" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 171.63 387.75 L 171.03 379.95 L 173.61 382.89 L 177.51 382.58 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 183.38 358.84 L 183.99 366.64 L 181.4 363.7 L 177.5 364 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 406.97 372.68 L 401.42 354.1" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 408.47 377.72 L 403.11 372.01 L 406.97 372.68 L 409.82 370.01 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 399.92 349.07 L 405.28 354.78 L 401.42 354.1 L 398.57 356.78 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 84.98 466.04 L 123.8 435.18" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 80.88 469.3 L 84.18 462.21 L 84.98 466.04 L 88.53 467.69 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 127.91 431.91 L 124.61 439 L 123.8 435.18 L 120.25 433.53 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 456.11 425.28 L 505.1 465.93" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 452.07 421.93 L 459.7 423.7 L 456.11 425.28 L 455.23 429.09 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 509.14 469.29 L 501.52 467.51 L 505.1 465.93 L 505.99 462.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 153.63 L 285 146.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 285 158.88 L 281.5 151.88 L 285 153.63 L 288.5 151.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 141.12 L 288.5 148.12 L 285 146.37 L 281.5 148.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 103.63 L 285 96.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 285 108.88 L 281.5 101.88 L 285 103.63 L 288.5 101.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 91.12 L 288.5 98.12 L 285 96.37 L 281.5 98.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="20" y="590" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 620px; margin-left: 21px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Customer Inventory&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="80" y="624" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer Inventory&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="450" y="590" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 620px; margin-left: 451px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Customer Inventory&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="510" y="624" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer Inventory&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 80 583.63 L 80 566.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 80 588.88 L 76.5 581.88 L 80 583.63 L 83.5 581.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 80 561.12 L 83.5 568.12 L 80 566.37 L 76.5 568.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 510 583.63 L 510 566.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 510 588.88 L 506.5 581.88 L 510 583.63 L 513.5 581.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 510 561.12 L 513.5 568.12 L 510 566.37 L 506.5 568.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;ellipse cx="290" cy="345" rx="50" ry="35" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 345px; margin-left: 241px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Git &lt;br&gt;Repository&lt;br&gt;Jenkisfile&lt;br&gt;scripts&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="290" y="349" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Git&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 133.46 509.65 L 236.54 350.35" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 130.61 514.06 L 131.47 506.28 L 133.46 509.65 L 137.35 510.09 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 239.39 345.94 L 238.53 353.72 L 236.54 350.35 L 232.65 349.91 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 343.67 350.2 L 456.33 509.8" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 340.64 345.91 L 347.54 349.61 L 343.67 350.2 L 341.82 353.65 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 459.36 514.09 L 452.46 510.39 L 456.33 509.8 L 458.18 506.35 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank" rel="noopener noreferrer"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-configure-agent"&gt;Configure Agent&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Jenkins has a plugin called &lt;a href="https://plugins.jenkins.io/ssh-slaves/"&gt;SSH Build Agent&lt;/a&gt;, that allows you to configure a Linux agent, communicating with Jenkins master in SSH. For Window agent, it uses JNLP to communicate with master, which is outside of our scope of discussion. As I touched on in &lt;a href="https://www.digihunch.com/2020/10/automated-deployment-pipeline-2-of-2/"&gt;previous post&lt;/a&gt;, Jenkins uses its own implementation of SSH protocol to achieve this. This means that it cannot re-use the configurations in ~/.ssh/config and thus the ability to do SSH chaining is eliminated. This is incompatible with our architecture so I have to register Jenkins agent using a different launch method &amp;#8220;Launch agent via execution of command on the master&amp;#8221;. The execution of command on master can still leverage OpenSSH config file. In order to do so, we must copy the agent.jar file to the remote agent first (URL is ${JENKINS_URL}/jnlpJars/agent.jar). Then use SSH command to call the jar file from agent (aka slave) machine. You may add some java argument for troubleshooting. Below is an example configuration for the node.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="2658" height="1650" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-6.png" alt="" class="wp-image-1736" style="width:0px"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I shall also note that if the remote agent is a different operating system where the path of bash might be different, then you need to include the directory of bash executable in PATH environment variable. That can be done as in the screenshot above (PATH=${PATH:/usr/bin}). If this is incorrect, you might run into issues when running sh step in Jenkins pipeline. Here is an &lt;a href="https://qiita.com/xishan/items/881f1e02628170801f4a"&gt;article&lt;/a&gt; about this. To translate that page, the symptom of this issue includes:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Error from pipeline execution that says the following, which is very generic:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;process apparently never started in /home/dhunch/jenkins/workspace/site-remote-job@tmp/durable-b997d26c &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(running Jenkins temporarily with -Dorg.jenkinsci.plugins.durabletask.BourneShellScript.LAUNCH_DIAGNOSTICS=true might make the problem clearer)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Job status shows:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hudson.AbortException: script returned exit code -2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution.handleExit(DurableTaskStep.java:659)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution.check(DurableTaskStep.java:605)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution.run(DurableTaskStep.java:549)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.lang.Thread.run(Thread.java:834)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Threaddump (only available during execution) shows:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Thread #6&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at DSL.sh(awaiting process completion in /home/dhunch/jenkins/workspace/site-remote-job/durable-b997d26c; recurrence period: 9543ms; check task scheduled; cancelled? false done? false)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at WorkflowScript.run(WorkflowScript:9)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at DSL.script(Native Method)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This issue is tricky because none of the symptoms above make you think about the environment variable defined for the agent, and that the issue does not occur until you actually execute a Jenkins pipeline, usually well after the node agent is registered, and only impacts shell step (sh). It is recommended to test this with sh steps in pipeline.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-configure-repository"&gt;Configure Repository&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Our example job executes service script pulled from SCM, on a remote agent. Then the master pulls the result file to itself. The git repository can be set in Jenkins job, where you specify that the script named Jenkinsfile (from the repo) is the pipeline file that needs to be executed.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1428" height="756" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-5.png" alt="" class="wp-image-1734"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This is fairly simple, but what makes the situation more complex is the following few requirements:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;it&amp;#8217;s the agent node that needs git (installed) and clone to repo;&lt;/li&gt;&#10;&lt;li&gt;the master does not need (and should not attempt to) clone from repo;&lt;/li&gt;&#10;&lt;li&gt;the master needs to do its job without having to pull SCM;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We need to be able to specify whether each step needs to pull from SCM. The following pipeline syntax shows how this is done:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; agent_dir &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;initial_value&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipeline &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent none&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options &lt;span style="color:#f92672"&gt;{&lt;/span&gt; skipDefaultCheckout&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stages &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Execute Job&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; label &lt;span style="color:#e6db74"&gt;&amp;#39;remote-agent-customer1&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options &lt;span style="color:#f92672"&gt;{&lt;/span&gt; skipDefaultCheckout&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#39;Executing job on node&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#39;whoami &amp;amp;&amp;amp; pwd&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; script &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent_dir &lt;span style="color:#f92672"&gt;=&lt;/span&gt; sh&lt;span style="color:#f92672"&gt;(&lt;/span&gt;returnStdout: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt; script: &lt;span style="color:#e6db74"&gt;&amp;#39;echo -n ${WORKSPACE}&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// the variable should not include carriage return&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;echo ${agent_dir}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Pull Result&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; label &lt;span style="color:#e6db74"&gt;&amp;#39;master&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options &lt;span style="color:#f92672"&gt;{&lt;/span&gt; skipDefaultCheckout&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt; &lt;span style="color:#75715e"&gt;// no need to pull scm to this agent&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#39;Pulling job below&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;echo ${agent_dir}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;scp dhunch@site1:\&amp;#34;${agent_dir}\&amp;#34;/result.csv ./&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In this example pipeline file, we declare agent none for the pipeline, then an agent for each specific stage. we also specify the option skipDefaultCheckout as true at the step where pulling from SCM is not needed. This allows us to finish job with multiple agent, and only pull from SCM as needed. This snippet also exemplifies how to declare a variable, assign it from stdout from one agent, and persist the value across ensuing stages.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-store-result"&gt;Store Result&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The reason we run pipeline jobs on this remote agent is because it is sitting in customer network and has local direct access to data. So this is perfect for situation such as data analytical jobs, which access to database on local network and store result. We need to pull the result file back to agent and make it available on Jenkins. This is so similar to &amp;#8220;archive artifact&amp;#8221; task (commonly seen in CI process) that we can simply use its plugin to achieve what we need. Before archive artifact, we need to pull it to local (master), as shown in the example code above. After that, we need another stage to archive the result.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Archive Result&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; label &lt;span style="color:#e6db74"&gt;&amp;#39;master&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options &lt;span style="color:#f92672"&gt;{&lt;/span&gt; skipDefaultCheckout&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt; &lt;span style="color:#75715e"&gt;// no need to pull scm to this agent&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; archiveArtifacts artifacts: &lt;span style="color:#e6db74"&gt;&amp;#39;*.csv&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt;onlyIfSuccessful: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt;fingerprint: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;I believe there is plugins to compress artifacts as well.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At the end I&amp;#8217;d like to reiterate my perception about Jenkins. It is a very generic and adaptive automation platform that originally evolved from use cases in build automation. Due to this original root, many components in Jenkins are named around Continuous Integration use cases, such as the &amp;#8220;build&amp;#8221; button, and the &amp;#8220;archiveArtifacts&amp;#8221; step. These misnomers underplays what Jenkins can potentially do in continuous deployment or other automation scenarios. It is important for automation engineers to understand Jenkins components and plugins, through their functionalities and not by the name, and therefore make creative use of Jenkins as automation engine in all scenarios. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;An alternative to this proposed pipeline would be Ansible Tower, a commercial project based on open-source Ansible, but with nice UI support. Ansible Tower is Ansible oriented, and it does not have everything that Jenkins can do. It should still be a decent alternative given the proposes pipeline uses Ansible a lot.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/10/automated-deployment-pipeline-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Automated Deployment Pipeline 2 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/10/host-legacy-application-in-docker-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Host legacy application in Docker 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Automated Deployment Pipeline 2 of 3</title><link>https://www.digihunch.com/2020/10/automated-deployment-pipeline-2-of-2/</link><pubDate>Tue, 06 Oct 2020 22:05:00 -0400</pubDate><guid>https://www.digihunch.com/2020/10/automated-deployment-pipeline-2-of-2/</guid><description>&lt;p class="wp-block-paragraph"&gt;In this posting, we continue to discuss Jenkins&amp;#8217; ability to automate deployment routines. Jenkins supports freestyle project out of the box, as well as Pipeline with several plugins. Freestyle project allows user to specify multiple steps on UI. This does not scale well when your entire process involves many steps. As explained on Jenkins&amp;#8217; website:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;While standard Jenkins “freestyle” jobs support simple continuous integration by allowing you to define sequential tasks in an application lifecycle, they do not create a &lt;strong&gt;persistent record of execution&lt;/strong&gt;, enable one script to address all the steps in a complex workflow, or confer the other advantages of pipelines.&lt;br&gt;In contrast to freestyle jobs, pipelines enable you to define the whole application lifecycle. Pipeline functionality helps Jenkins to support continuous delivery (CD). The Pipeline plugin was built with requirements for a flexible, extensible, and script-based CD workflow capability in mind.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So although freestyle projects are easy to set up, and can do technically everything that Jenkins pipeline can do, the major advantage of Jenkins Pipeline is the ability to manage multiple-step as code, and version control the pipeline-as-code. Here is some more &lt;a href="https://www.jenkins.io/blog/2017/01/19/converting-conditional-to-pipeline/"&gt;information&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We will start with a freestyle project to understand Jenkins&amp;#8217; ability and then advance to building pipelines.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-freestyle-projects"&gt;Freestyle projects&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As mentioned, Jenkins calls a task a &amp;#8220;build&amp;#8221;, and the build can be triggered in a variety of ways:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Time schedule (with or without parameter)&lt;/li&gt;&#10;&lt;li&gt;Remotely via API&lt;/li&gt;&#10;&lt;li&gt;On completion of other projects&lt;/li&gt;&#10;&lt;li&gt;Poll SCM for changes&lt;/li&gt;&#10;&lt;li&gt;Commit to SCM&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are also several ways to execute a job:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Execute Shell command, batch command or groovy script&lt;/li&gt;&#10;&lt;li&gt;Invoke Ansible adhoc command, playbook or vault&lt;/li&gt;&#10;&lt;li&gt;Conditional on specified boolean value, file existence, etc&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After the job one can specify post-build jobs, for example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;start other build projects&lt;/li&gt;&#10;&lt;li&gt;notification of various means&lt;/li&gt;&#10;&lt;li&gt;publishing result file&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In deployment, it is common task to execute a task over SSH, using SSH command or invoke Ansible command with Ansible plugin. The former fits simple command line tasks. For example:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="2036" height="738" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-1.png" alt="" class="wp-image-1658" style="width:812px;height:auto"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Ansible plugin is good for more steps and more complicated inventory hierarchies. This &lt;a href="https://www.digihunch.com/2020/05/revamp-ansible-directory-for-scalability-1-of-2/"&gt;post&lt;/a&gt; includes an example of an inventory involving multiple layers. The other limitation with SSH command is lack of a straightforward configuration to escalate privilege and run remote command. On the other hand, Ansible addressed this with become method. Below is a screenshot &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="950" height="837" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-2.png" alt="" class="wp-image-1659" style="width:785px;height:auto"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In order to use Ansible command, you will also need to specify where the Ansible binary in &lt;a href="https://medium.com/devops-process-and-tools/configure-jenkins-job-with-ansible-jenkins-plugin-to-setup-ci-for-ansible-playbooks-3ed23137d314"&gt;Global Tool Configuration&lt;/a&gt;. You will also need to store vault credential in Jenkins credential store so it&amp;#8217;s not being prompted.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-jenkins-pipeline"&gt;Jenkins Pipeline&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Jenkins pipeline allows one to describe actions in a pipeline in groovy &lt;a href="http://docs.groovy-lang.org/docs/latest/html/documentation/core-domain-specific-languages.html"&gt;Domain Specific Language (DSL)&lt;/a&gt;. There are two styles of pipelines:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Declarative pipeline&lt;/strong&gt; is identified by a block named &amp;#8216;pipeline&amp;#8217;, it is relatively new and supports the pipeline-as-code concept. It can be stored as Jenkinsfile in code repository or edited in Jenkins&amp;#8217; UI.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Scripted pipeline&lt;/strong&gt; is identified by a block named &amp;#8216;node&amp;#8217;, it is the conventional format and can only be edited in Jenkins&amp;#8217; UI.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To fully understand the two styles of pipelines you also need to know &lt;a href="https://en.wikipedia.org/wiki/Declarative_programming"&gt;declarative programming&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Imperative_programming"&gt;imperative programming&lt;/a&gt;. Read &lt;a href="https://www.jenkins.io/doc/book/pipeline/syntax/"&gt;this&lt;/a&gt; instruction for a better explanation. &lt;a href="https://e.printstacktrace.blog/jenkins-scripted-pipeline-vs-declarative-pipeline-the-4-practical-differences/"&gt;Here&lt;/a&gt; is another good one that focus on their differences.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Jenkins&amp;#8217; plugin provides UI components for user to input information (e.g. Invoke Ansible Command), now with Jenkins pipelines, plugins can provide step functions in order to help Jenkins user. &lt;a href="https://www.jenkins.io/doc/pipeline/steps/"&gt;This&lt;/a&gt; page from Jenkins lists the most common ones, many of which requires plugin installation. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One example of step function is &lt;a href="https://www.jenkins.io/doc/pipeline/steps/ssh-steps/"&gt;SSH Pipeline Steps&lt;/a&gt;. It allows one to issue SSH connection from Jenkinsfile. However, in our deployment scenarios, it has some limitations:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Unable to match a group of host by specific pattern&lt;/li&gt;&#10;&lt;li&gt;Cannot use host name aliases&lt;/li&gt;&#10;&lt;li&gt;SSH tunneling is not supported&lt;/li&gt;&#10;&lt;li&gt;No means of privilege escalation&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Out of these 1 through 3 are due to the fact that SSH step function does not use the OpenSSH configuration on the machine. The&amp;nbsp;&lt;a href="https://wiki.jenkins-ci.org/display/JENKINS/SSH+Slaves+plugin"&gt;jenkins-ssh-slaves plugin&lt;/a&gt;&amp;nbsp;uses&amp;nbsp;&lt;a href="https://github.com/jenkinsci/trilead-ssh2"&gt;trilead SSH2 implementation&lt;/a&gt;&amp;nbsp;written in Java. Only OpenSSH implementation uses ~/.ssh/config file. This creates problem whenever SSH tunneling is needed. For example, to register a remote slave node via SSH tunneling, we need to select &amp;#8220;Launch agent via &lt;a href="https://support.cloudbees.com/hc/en-us/articles/234491548-Run-SSH-Agent-using-a-command-on-Master"&gt;execution of command on the master&lt;/a&gt;&amp;#8221; instead of &amp;#8220;Launch agent via SSH&amp;#8221; as launch method. &lt;a href="https://support.cloudbees.com/hc/en-us/articles/234491548-Run-SSH-Agent-using-a-command-on-Master"&gt;Here&lt;/a&gt; is an instruction and below is what it looks like:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="727" height="405" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-3.png" alt="" class="wp-image-1698" style="width:755px;height:auto"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Similarly, if we use ssh-agent plugin as tool for deployment, we cannot use any configuration made by OpenSSH. Therefore Ansible in Jenkins Pipeline is a better tool for deployment because it can use OpenSSH.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-pipelines-with-ansible"&gt;Pipelines with Ansible&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this section I demonstrate the use of Ansible playbook and adhoc command in pipeline through two examples. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In example 1, the job pulls authorized key file from SCM, and then use a playbook from SCM, to push the key file to all servers in the specified inventory. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Here is the playbook file for example 1:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# Example:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# ansible-playbook -l all -i ~/ansible/inventories/bh.yml push-key.yml --ask-vault-pass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- name: push key to target&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: all&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tasks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: sync key to host&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; copy:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; src: &amp;#34;{{authorized_keys_src}}&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dest: &amp;#34;/home/dhunch/.ssh/authorized_keys&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; force: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mode: 0600&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Here is the pipeline script (note ampersand is mistakenly displayed as &amp;amp;amp; in the box below):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; java.net.URLEncoder;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipeline {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent any&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; skipDefaultCheckout(&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BITBUCKET_CREDS &lt;span style="color:#f92672"&gt;=&lt;/span&gt; credentials(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;bitbucket&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//BITBUCKET_CREDS_USR and BITBUCKET_CREDS_PSW are set&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BITBUCKET_CREDS_USR &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;${BITBUCKET_CREDS_USR}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BITBUCKET_CREDS_PSW &lt;span style="color:#f92672"&gt;=&lt;/span&gt; URLEncoder.&lt;span style="color:#a6e22e"&gt;encode&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;${BITBUCKET_CREDS_PSW}&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;) &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// if password contains special character we need to url encode it. e.g. @-&amp;gt;%40&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stages {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Start&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Starting pipeline ...&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deleteDir()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Prep&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wrap(&lt;span style="color:#f92672"&gt;[&lt;/span&gt;$class: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;MaskPasswordsBuildWrapper&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;, varPasswordPairs: &lt;span style="color:#f92672"&gt;[[&lt;/span&gt;password: &lt;span style="color:#e6db74"&gt;&amp;#34;${BITBUCKET_CREDS_PSW}&amp;#34;&lt;/span&gt;, var: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;RANDOM&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]]]&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// MaskPasswordsBuildWrapper requires Mask Passwords Plugin and is to mask specific string in console output. &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Otherwise BITBUCKET_CREDS_PSW will display in the clear&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;git init &amp;amp;&amp;amp; git config core.sparsecheckout true&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;git remote add origin https://$BITBUCKET_CREDS_USR:${BITBUCKET_CREDS_PSW}@bitbucket.org/vendorcompoany/configmanagerepo.git&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;echo &amp;#39;public_keys/*&amp;#39; &amp;gt;&amp;gt; .git/info/sparse-checkout&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Downloading key file&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;git pull --depth=1 origin master&amp;#34;&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Deploy &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 1&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deploying &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 1 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansiblePlaybook (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; installation: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Ansible on Mac&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; playbook: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;${WORKSPACE}&lt;span style="color:#f92672"&gt;/&lt;/span&gt;public_keys&lt;span style="color:#f92672"&gt;/&lt;/span&gt;push&lt;span style="color:#f92672"&gt;-&lt;/span&gt;key.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; inventory: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;~/&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;/&lt;/span&gt;inventories&lt;span style="color:#f92672"&gt;/&lt;/span&gt;site1.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vaultCredentialsId: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;-&lt;/span&gt;vault&lt;span style="color:#f92672"&gt;-&lt;/span&gt;pass&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extraVars: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;authorized_keys_src: &lt;span style="color:#e6db74"&gt;&amp;#34;$WORKSPACE/public_keys/authorized_keys&amp;#34;&lt;/span&gt;,&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deployed &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 1 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Deploy &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 2&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deploying &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 2 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansiblePlaybook (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; installation: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Ansible on Mac&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; playbook: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;${WORKSPACE}&lt;span style="color:#f92672"&gt;/&lt;/span&gt;public_keys&lt;span style="color:#f92672"&gt;/&lt;/span&gt;push&lt;span style="color:#f92672"&gt;-&lt;/span&gt;key.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; inventory: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;~/&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;/&lt;/span&gt;inventories&lt;span style="color:#f92672"&gt;/&lt;/span&gt;site2.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vaultCredentialsId: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;-&lt;/span&gt;vault&lt;span style="color:#f92672"&gt;-&lt;/span&gt;pass&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extraVars: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;authorized_keys_src: &lt;span style="color:#e6db74"&gt;&amp;#34;$WORKSPACE/public_keys/authorized_keys&amp;#34;&lt;/span&gt;,&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deployed &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 2 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Deploy &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 3&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deploying &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 3 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansiblePlaybook (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; installation: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Ansible on Mac&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; playbook: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;${WORKSPACE}&lt;span style="color:#f92672"&gt;/&lt;/span&gt;public_keys&lt;span style="color:#f92672"&gt;/&lt;/span&gt;push&lt;span style="color:#f92672"&gt;-&lt;/span&gt;key.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; inventory: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;~/&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;/&lt;/span&gt;inventories&lt;span style="color:#f92672"&gt;/&lt;/span&gt;site3.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vaultCredentialsId: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;-&lt;/span&gt;vault&lt;span style="color:#f92672"&gt;-&lt;/span&gt;pass&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extraVars: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;authorized_keys_src: &lt;span style="color:#e6db74"&gt;&amp;#34;$WORKSPACE/public_keys/authorized_keys&amp;#34;&lt;/span&gt;,&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deployed &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 3 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; post {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; always {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cleanWs()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In this example, we pull a sub-directory from git repo. We use two tricks to minimize amount of traffic. First, we use git pull with depth=1 so only the required recent commits are pulled, not the entire history. Second we use sparse checkout to get result from a sub-directory, not the entire repo.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When connecting to repo, password is required. We pull the credential from Jenkins&amp;#8217; credential store and they are masked by default. However, the password must be converted to URL string when used in git remote add origin. Otherwise if the password contains special character the URL will not work. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This modification to password brings about another challenge, password masking during the job execution. We will have to build our own wrapper function using class &lt;em&gt;MaskPasswordsBuildWrapper&lt;/em&gt; in order to mask any variable.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In example 2, we wrap an Ansible step function to call adhoc command to check version on all hosts. Here is the pipeline script:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;def HunchVersionQuery(pattern, siteinventory){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; step([&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $class: &amp;#39;AnsibleAdHocCommandBuilder&amp;#39;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansibleName: &amp;#39;Ansible on Mac&amp;#39;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; inventory: [$class: &amp;#39;InventoryPath&amp;#39;,path: siteinventory],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hostPattern: pattern,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; module: &amp;#39;shell&amp;#39;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; command: &amp;#39;cat /etc/*release&amp;#39;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; forks: 1,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vaultCredentialsId: &amp;#39;ansible-vault-pass&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipeline {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent any&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stages {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&amp;#39;Start&amp;#39;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &amp;#39;Staring Pipeline&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&amp;#39;Query Site1&amp;#39;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps { HunchVersionQuery(&amp;#39;*app&amp;#39;,&amp;#39;~/ansible/inventories/site1.yml&amp;#39;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&amp;#39;Query Site2&amp;#39;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps { HunchVersionQuery(&amp;#39;*app&amp;#39;,&amp;#39;~/ansible/inventories/site2.yml&amp;#39;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&amp;#39;Query Site3&amp;#39;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HunchVersionQuery(&amp;#39;*app&amp;#39;,&amp;#39;~/ansible/inventories/site3.yml&amp;#39;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Because &lt;a href="https://www.jenkins.io/doc/pipeline/steps/ansible/"&gt;Ansible plugin&lt;/a&gt; does not provide a warpper function for adhoc command step, we will have to build our own wrapper function in the above code.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/09/automated-deployment-pipeline-1-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Automated Deployment Pipeline 1 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/10/automated-deployment-pipeline-3-of-3/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Automated Deployment Pipeline 3 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Automated Deployment Pipeline 1 of 3</title><link>https://www.digihunch.com/2020/09/automated-deployment-pipeline-1-2/</link><pubDate>Wed, 30 Sep 2020 22:04:00 -0400</pubDate><guid>https://www.digihunch.com/2020/09/automated-deployment-pipeline-1-2/</guid><description>&lt;h3 class="wp-block-heading"&gt;The business case&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You launched a software application. You installed it on two customer sites. You support the application mostly by SSH to customer server and run Bash commands, or slightly better, Bash scripts. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The product is a hit to the market. You hired 20 support specialists in a customer service department. The dream client came through: an enterprise giving you a fleet of 100 servers to deploy your application on.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;More staff, more business, more installations, more incidents, but the same old command driven steps. Problems:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Non-standard support procedures. Every one takes notes and everyone&amp;#8217;s notes are slightly different.&lt;/li&gt;&lt;li&gt;Information sharing among team members are ad hoc, and at high level.&lt;/li&gt;&lt;li&gt;Post-mortem discussion is driven by memory and command fragments, instead of evidence end-to-end&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If that looks like your organization, chances are you also suffer from some secondary damages over the long term, such as:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Downtime resolutions rely on the knowledgeable few&lt;/li&gt;&lt;li&gt;Documentation helps. But it never catches up to the latest version of application&lt;/li&gt;&lt;li&gt;Lack of auditing of commands during support&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I propose an automation scheme to existing support and deployment practice. This automation scheme combines a suite of common technologies, such as Bash, Python, Ansible, OpenSSH and Jenkins. The automation allows the department to, either fully or partially, operationalize the steps in support and deployment, and eventually shift towards agile practice. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Bash, Python and Ansible&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Bash script is based on shell command, perfect for running critical system tasks such as volume management. When it turns into a script, it can be cumbersome, especially with complex data structure. Python, as a tool for system administration, is a good complement to that.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Python 2 comes with most Linux distributions, and is also a dependency of other built-in tools such as YUM. Python3 can be installed easily from default YUM repositories. Both Python2 and Python3 can exist on the same operating system, although new module development are now shifted to Python3. Python&amp;#8217;s syntax is very simple and offers object-oriented programming ability. Moreover, there is an entire open-source community behind Python, which offers modules in every aspect of IT (for example, Datastax has a &lt;a href="https://www.digihunch.com/2020/06/iterate-through-cassandra-table-with-datastax-python-driver/"&gt;driver module&lt;/a&gt; for connecting to Cassandra). Those modules are installed with PIP tool, or PIP3 for python3.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both bash and Python executes on local machine. To run them on remote servers over SSH. You want to have a list of target hosts, and specify which one to execute the script against. This is where Ansible comes in handy. Ansible is superior in the following aspects:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Free and open-source, with commercial alternative (Towers);&lt;/li&gt;&lt;li&gt;Inventory management (inventory);&lt;/li&gt;&lt;li&gt;Desire state engine (roles) &lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ansible is built on Python and is agent-less. Connectivity to remote host is done via secure shell so it can take advantage of existing SSH configurations. Job execution on the target machine is done through Python. With Python you can also develop custom module in Ansible. For some use cases in customer support with Ansible, refer to my two &lt;a href="https://www.digihunch.com/2020/05/revamp-ansible-directory-for-scalability-1-of-2/"&gt;previous postings&lt;/a&gt; about Ansible at scale.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Jenkins&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The tools above forms a package for automation. The issue is that all of them are command-line based. Any task that requires Ansible requires the IT professional craft up long command, such as running playbook, executing a role, or ad-hoc command. This is inconvenient when a task needs to be done during an incident. Such tasks also require trained professional with the relevant skills.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These tasks can be stored in, or initiated by &lt;a href="https://www.jenkins.io/"&gt;Jenkins&lt;/a&gt;. Although Jenkins is well known for build automation in continuous integration, it is automation engine for any command-line based IT tasks. The button to start such tasks in Jenkins UI is called &amp;#8220;Build&amp;#8221;, which is also a misnomer that underplays Jenkins&amp;#8217; versatility: building application from source code is just one of the many IT tasks that involves multiple long running commands. In this and next article we introduce Jenkins as an engine for deployment automation. &lt;/p&gt;&#10;&lt;p class="has-text-align-center has-background wp-block-paragraph" style="background-color:#d5edf7"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="587" viewBox="-0.5 -0.5 587 567" style="max-width:100%;max-height:567px;"&gt;&lt;defs&gt;&lt;filter id="dropShadow"&gt;&lt;feGaussianBlur in="SourceAlpha" stdDeviation="1.7" result="blur"&gt;&lt;/feGaussianBlur&gt;&lt;feOffset in="blur" dx="3" dy="3" result="offsetBlur"&gt;&lt;/feOffset&gt;&lt;feFlood flood-color="#3D4574" flood-opacity="0.4" result="offsetColor"&gt;&lt;/feFlood&gt;&lt;feComposite in="offsetColor" in2="offsetBlur" operator="in" result="offsetBlur"&gt;&lt;/feComposite&gt;&lt;feBlend in="SourceGraphic" in2="offsetBlur"&gt;&lt;/feBlend&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;g filter="url(#dropShadow)"&gt;&lt;rect x="220" y="0" width="130" height="190" rx="19.5" ry="19.5" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 7px; margin-left: 221px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Vendor&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="19" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Vendor&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="420" y="390" width="160" height="160" rx="24" ry="24" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 397px; margin-left: 421px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Customer B&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="500" y="409" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer B&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="400" width="160" height="160" rx="24" ry="24" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 407px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Customer A&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="80" y="419" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer A&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="240" y="110" width="90" height="30" rx="4.5" ry="4.5" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 125px; margin-left: 241px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;SSH Proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="129" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;SSH Proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="240" y="30" width="90" height="60" rx="9" ry="9" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 60px; margin-left: 241px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Jenkins&lt;br&gt;Ansible&lt;br&gt;OpenSSH&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="64" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Jenkins&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="285" cy="190" rx="30" ry="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 190px; margin-left: 256px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;VPN&lt;br&gt;Gateway&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="194" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;VPN&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="150" cy="410" rx="30" ry="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 410px; margin-left: 121px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;VPN&lt;br&gt;Gateway&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="414" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;VPN&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="430" cy="400" rx="30" ry="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 400px; margin-left: 401px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;VPN&lt;br&gt;Gateway&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="430" y="404" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;VPN&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 247.5 280 C 217.5 280 210 310 234 316 C 210 329.2 237 358 256.5 346 C 270 370 315 370 330 346 C 360 346 360 322 341.25 310 C 360 286 330 262 303.75 274 C 285 256 255 256 247.5 280 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 310px; margin-left: 211px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Internet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="314" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Internet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="20" y="470" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 500px; margin-left: 21px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Server Fleet Managed by Vendor&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="80" y="504" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Server Fleet Managed&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="440" y="470" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 500px; margin-left: 441px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Server Fleet Managed by Vendor&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="500" y="504" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Server Fleet Managed&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 286.02 255.63 L 285.18 226.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 286.17 260.88 L 282.47 253.99 L 286.02 255.63 L 289.47 253.79 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285.03 221.12 L 288.73 228.01 L 285.18 226.37 L 281.73 228.21 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 176.2 384.82 L 224.52 346.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 172.09 388.09 L 175.39 380.99 L 176.2 384.82 L 179.74 386.47 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 228.63 343.1 L 225.33 350.19 L 224.52 346.37 L 220.97 344.72 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 403.86 374.76 L 358.93 338.03" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 407.92 378.08 L 400.29 376.36 L 403.86 374.76 L 404.72 370.94 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 354.87 334.71 L 362.5 336.43 L 358.93 338.03 L 358.07 341.85 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 84.98 466.04 L 123.8 435.18" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 80.88 469.3 L 84.18 462.21 L 84.98 466.04 L 88.53 467.69 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 127.91 431.91 L 124.61 439 L 123.8 435.18 L 120.25 433.53 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 455.72 425.72 L 495.5 465.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 452 422 L 459.43 424.48 L 455.72 425.72 L 454.48 429.43 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 499.21 469.21 L 491.78 466.73 L 495.5 465.5 L 496.73 461.78 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 153.63 L 285 146.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 285 158.88 L 281.5 151.88 L 285 153.63 L 288.5 151.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 141.12 L 288.5 148.12 L 285 146.37 L 281.5 148.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 103.63 L 285 96.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 285 108.88 L 281.5 101.88 L 285 103.63 L 288.5 101.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 91.12 L 288.5 98.12 L 285 96.37 L 281.5 98.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank" rel="noopener noreferrer"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The infrastructure architecture is diagramed as above, and with the connection across Internet, the target hosts must be hardened properly in the following aspects:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Connectivity to remote host is via SSH chaining, through an SSH proxy;&lt;/li&gt;&lt;li&gt;Root login must be disabled for remote session or by password;&lt;/li&gt;&lt;li&gt;Service user may be shared, but must be authenticated by individual RSA key pair;&lt;/li&gt;&lt;li&gt;Service user connected remotely needs to escalate privilege by su if needed;&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I want to make a theoretical distinction between our topic here and continuous deployment. We simply focus on the technical side of deployment automation. Essentially automating a few bash scripts. On the other hand, a continuous deployment process is an extension to an existing continuous integration pipeline, with the vision to streamline the process end-to-end from code commit to production rollout. Implementing CI/CD pipelines should be approached as an organizational program rather than an individual technical initiative. &lt;a href="https://www.redhat.com/en/blog/integrating-ansible-jenkins-cicd-process"&gt;Here&lt;/a&gt; is a good technical overview on CI/CD pipeline with Jenkins and Ansible.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Security&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The security mechanism of this system is based on OpenSSH because the connectivity between servers are through SSH chaining. RSA key authentication must be used in order to encrypt traffic with password-less login. Connection to an SSH host can be done through a proxy server. Below is an example of SSH configuration:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Include customer1.config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Include customer2.config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host *&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IdentityFile ~/.ssh/id_rsa&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerAliveInterval &lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerAliveCountMax &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Compression yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ControlPersist 3h&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ControlPath ~/.ssh/sockets/%r@%h-%p&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host gateway&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hostname support.digihunch.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User jdoe&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Port &lt;span style="color:#ae81ff"&gt;2223&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host customer-server-0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hostname 192.168.201.12&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User support&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ProxyCommand ssh -W %h:%p gateway&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Open SSH configuration file (~/.ssh/config) needs to be configured properly with useful host names and aliases. To prevent the config files from growing too long, include statement can be used to reference other configuration file (available with OpenSSH v7.3 sp1 and up). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The host names (as well as aliases) listed in SSH configuration can be directly referenced in Ansible inventory, allowing Ansible (and Jenkins) to reference site by alias and connect to target host through proxy.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Plugins&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Jenkins has a community that develops a variety of plugins, which makes Jenkins the most powerful automation platform. Here are some examples of useful plugins:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Audit Trail: output job execution history to file or Elasticsearch;&lt;/li&gt;&lt;li&gt;Credentials: stores credentials in Jenkins;&lt;/li&gt;&lt;li&gt;Pipeline: build declarative (new) or scripted (old) pipeline for Jenkins jobs;&lt;/li&gt;&lt;li&gt;Simple Theme: just a theme but allows console output to be dark (using CSS);&lt;/li&gt;&lt;li&gt;Job Configuration History: job configuration audit;&lt;/li&gt;&lt;li&gt;Mask password: mask variables (including password) from console output &lt;/li&gt;&lt;li&gt;Ansible: invokes ad-hoc commands and playbooks&lt;/li&gt;&lt;li&gt;SSH agent, SSH pipeline steps, SSH credentials: features related in SSH in Jenkins pipelines.&lt;/li&gt;&lt;li&gt;Purge Job History: purge all of build history, or purge&lt;a href="https://support.cloudbees.com/hc/en-us/articles/215549798-Best-Strategy-for-Disk-Space-Management-Clean-Up-Old-Builds"&gt; by time &lt;/a&gt;and number of old builds.&lt;/li&gt;&lt;li&gt;Parameterized Scheduler: schedule to run a job and provide parameter&lt;/li&gt;&lt;li&gt;Workspace cleanup: clean up workspace when invoked.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the next article, we will go over some common job configurations.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/09/log-file-navigator-lnav/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Log file navigator (lnav)&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/10/automated-deployment-pipeline-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Automated Deployment Pipeline 2 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Common local Git operations</title><link>https://www.digihunch.com/2020/04/common-local-git-operations/</link><pubDate>Wed, 08 Apr 2020 11:54:00 -0400</pubDate><guid>https://www.digihunch.com/2020/04/common-local-git-operations/</guid><description>&lt;p class="wp-block-paragraph"&gt;This is a summary of concepts in common Git operations. We will discuss brach, merge, rebase, cherrypick, stash and reset. Then we&amp;#8217;ll discuss pull, fetch, and push.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-commit-branch-and-head"&gt;Commit, Branch and HEAD&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When you run &amp;#8220;git commit&amp;#8221;, the following happens:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Git checksums each subdirectory, and stores them as a tree object (file path and name) and blob object (file content) in Git repository;&lt;/li&gt;&lt;li&gt;Git creates a commit object that has the metadata and a pointer to the root project tree; or if this is not the first commit, the pointer will point to the commit immediately before it&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The operations above should form a chain of commit. It can be a long chain and may diverge into branches. In Git semantics however, a branch is simply a lightweight, movable pointer to one of the commits. The default branch name in Git is master. A Git repository may contain multiple branches and the name master itself does not suggest any privilege. There is also a special pointer called HEAD, which indicates the branch you are currently working on. So branch is essentially a pointer to a commit; HEAD is essentially a pointer to a branch. &amp;#8220;git checkout&amp;#8221; can switch branch that HEAD points to. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-basic-merge"&gt;Basic Merge&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One type of basic merge simply moves branch pointer from one commit to another (along the same chain) without creating any commit. Here is a diagram before basic merge:&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://git-scm.com/book/en/v2/images/basic-branching-4.png" alt="Hotfix branch based on `master`."/&gt;&lt;figcaption&gt;Before basic merge, Hotfix branch is based on&amp;nbsp;&lt;code&gt;master&lt;/code&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The following command performs basic merge:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git checkout master&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git merge hotfix&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Updating f42c576..3a0874c&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Fast-forward&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; index.html | 2 ++&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 1 file changed, 2 insertions(+)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then Git simply moves the pointer (named master) forward. There is no divergent work to move together, hence no chance of merge conflict. This type of basic merge is also called &amp;#8220;fast-forward&amp;#8221; merge.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://git-scm.com/book/en/v2/images/basic-branching-5.png" alt="`master` is fast-forwarded to `hotfix`."/&gt;&lt;figcaption&gt;After basic merge, &lt;code&gt;master&lt;/code&gt;&amp;nbsp;is fast-forwarded to&amp;nbsp;&lt;code&gt;hotfix&lt;/code&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The other type of merge involves reconciling divergent work together, which may or may not involve conflict. Suppose this is the commit tree to start with:&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://git-scm.com/book/en/v2/images/basic-merging-1.png" alt="Three snapshots used in a typical merge."/&gt;&lt;figcaption&gt;Three snapshots used in a typical merge&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The following commands perform the merge:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git checkout master&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Switched to branch &amp;#39;master&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git merge iss53&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Merge made by the &amp;#39;recursive&amp;#39; strategy.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;index.html | 1 +&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1 file changed, 1 insertion(+)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Instead of just moving the branch pointer forward, Git creates a new snapshot that results from this three-way merge and automatically creates a new commit that points to it. This is referred to as a merge commit, and is special in that it has more than one parent.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://git-scm.com/book/en/v2/images/basic-merging-2.png" alt="A merge commit."/&gt;&lt;figcaption&gt;A merge commit&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now that your work is merged in, you have no further need for the&amp;nbsp;&lt;code&gt;iss53&lt;/code&gt;&amp;nbsp;branch. You can close the issue in your issue-tracking system, and delete the branch:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git branch -d iss53&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;If commits from two respective branches changes the same file in different ways, then there is a merge conflict. In this case, Git cannot just create a merge commit. Instead it asks the user to resolve the conflict first. You have to choose either side of the change, or just merge the content yourself. At this point, if you introduce a change that does not appear in any parent, it is referred to as an evil merge. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Beyond the basic merge, there are more sophisticated merge conflict resolution tools covered in &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging"&gt;advanced merging&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-merge-and-rebase"&gt;Merge and Rebase&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are two ways to integrate changes from one branch to another. Merge and rebase. Suppose your commit chain diverge into a master branch and a feature branch. Merging (from feature to master) takes the content of feature branch and integrate it with master branch. &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git checkout master&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git merge feature&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When you rebase a feature branch onto master, you move the base of the feature branch to master branch’s ending point.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git checkout feature&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git rebase master&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After merge, you are still &lt;span style="text-decoration: underline;"&gt;on the same branch&lt;/span&gt;. The commits from other branch are integrated into the branch that you are already on. There is no change in any existing commits (history). After rebase, your base will be &lt;span style="text-decoration: underline;"&gt;moved to a different branch&lt;/span&gt;, along with the commits that you have made in the previous branch (since the diverge). In other words, by re-playing those commits on a different branch, it changed history.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://miro.medium.com/max/855/1*pzT4KMiZDOFsMOKH-cJjfQ.png" alt=""/&gt;&lt;figcaption&gt;merge vs rebase&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The chart above is stolen from &lt;a href="https://hackernoon.com/git-merge-vs-rebase-whats-the-diff-76413c117333"&gt;this article&lt;/a&gt;, which does a better job explain in detail the difference, pros and cons of merge and rebase. Merge does create a &amp;#8220;merge commit&amp;#8221;, and a git history full of merges can be cluttered. Rebase does not create an extra commit but since it changes the history of a branch, it has impact to other collaborators. It can be done in an interactive way (with -i switch). The golden rules of rebasing is covered in &lt;a href="https://www.atlassian.com/git/tutorials/merging-vs-rebasing"&gt;this&lt;/a&gt; article. One of the principles is that never perform a rebase on a public branch.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="600" height="335" src="https://www.digihunch.com/wp-content/uploads/2020/08/0.gif" alt="" class="wp-image-1196"/&gt;&lt;figcaption&gt;git operations&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h3 class="wp-block-heading" id="h-cherrypick"&gt;Cherrypick&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In a cherrypick operation, the current branch does not change. You simply pick interested commits from other branches to re-apply to your current branch. You may pick a single or a series of commits from other branch. These commits are not &amp;#8220;moved&amp;#8221; to your current branch. They remain intact. They are just re-played as new commit to current branch. Unlike rebase, there is no re-writing of history, hence not as dangerous.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-reset-and-stash"&gt;Reset and Stash&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Suppose you are working on a part of a project and it starts getting messy. There has been an urgent bug that needs your immediate attention. It is time to save your changes and switch branches. If you are okay to give up your uncommitted work, you may perform a reset, in one of the three modes covered in a &lt;a href="https://www.digihunch.com/2019/06/git-explained-1-of-2/"&gt;previous article&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;But most likely, you don’t want to do a commit of half-done work. The solution is git stash. Stashing is handy if you need to quickly switch context and work on something else but you&amp;#8217;re mid-way through a code change and aren&amp;#8217;t quite ready to commit. In the most basic workflow, you need to run this command to save your uncommitted (but staged) work. As soon as you stash your change, the working directory is clean with all uncommitted local changes saved elsewhere. You can perform any other Git operations, such as change branch. When you&amp;#8217;re ready to resume, you may pop the stash. Here is an example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git add .&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git stash&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git checkout correctbranch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git stash pop&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Instead of pop, you can also use apply to keep the changes in working directory. &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git stash apply&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;More details are on &lt;a href="https://www.atlassian.com/git/tutorials/saving-changes/git-stash"&gt;this page&lt;/a&gt; from Bitbutket.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-fetch-and-pull"&gt;Fetch and Pull&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A git fetch simply downloads blob data from remote so the .git directory comes in sync with the server. It does not attempt to update the local working directory. If there is staged or uncommitted local changes, fetch will not impact them. &lt;strong&gt;A git pull is essentially git fetch followed by git merge&lt;/strong&gt;. In addition to downloading blob data, it also updates local working directory. Therefore, there is a chance of merge conflict when the same file has been modified locally. Git will usually guide you through the merge conflict by flagging the conflict area in the file and let you decide the survival changes. For example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#! /usr/bin/env ruby&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;def hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&amp;lt; HEAD&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; puts &lt;span style="color:#e6db74"&gt;&amp;#39;hola world&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;=======&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; puts &lt;span style="color:#e6db74"&gt;&amp;#39;hello mundo&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; mundo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;end&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello&lt;span style="color:#f92672"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;You will be prompted in an editor session to reconcile the conflict. Once the file is saved, you will also need to do a &amp;#8220;merge commit&amp;#8221;, before you can pull again.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-push"&gt;Push&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Git push is the opposite of pull, where you merge local branch to the remote. (There is no opposite of fetch because there is no point to merge to remote without updating working directory, no collaborator works on the working directory on the server after all). If the local branch has fallen out of sync with the remote, there is a chance of merge error during git push. To minimize the chance of a merge during push, we can run a git pull before and reconcile any potential conflict locally. This is known as a pre-merge.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Visualizer&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I came across a great visualizer of commit chain &lt;a href="https://git-school.github.io/visualizing-git/"&gt;here&lt;/a&gt;. In the command panel type some git command and it will print the commit graph for you&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/03/ntlm-and-kerberos/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;NTLM and Kerberos protocols&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/04/how-memory-usage-adds-up-in-linux/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;How memory usage adds up in Linux&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Clean up Git repository</title><link>https://www.digihunch.com/2019/10/clean-up-your-git-repository/</link><pubDate>Sat, 26 Oct 2019 20:33:00 -0400</pubDate><guid>https://www.digihunch.com/2019/10/clean-up-your-git-repository/</guid><description>&lt;p class="wp-block-paragraph"&gt;A BitBucket repo has a hard limit of 2GB in size, and soft limit of 1GB. This is&amp;nbsp;&lt;a href="https://confluence.atlassian.com/bitbucket/what-kind-of-limits-do-you-have-on-repository-file-size-273877699.html"&gt;not expandable&lt;/a&gt;&amp;nbsp;as per&amp;nbsp;Bitbucket and contributors will start receiving warnings once soft limit is reached. We can tell the usage of a repo from the landing page of the repo in BitBucket.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" src="https://www.digihunch.com/wp-content/uploads/2020/03/git.png" alt="" class="wp-image-694" width="202" height="300"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Git is a distributed version control system for source code management, which implies the followings:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;It is intended for source code, or configuration code; but not for storing build artifacts, or installers;&lt;/li&gt;&#10;&lt;li&gt;Git remembers every single commit, including the ones associated with large files;&lt;/li&gt;&#10;&lt;li&gt;Even a contributor deletes a large file (&amp;#8220;git rm filename&amp;#8221;) after commit, the large file is only removed from the HEAD. The historical commit still stores the file. After all, the whole point of version control is to survive crazy deletion.&lt;/li&gt;&#10;&lt;li&gt;distributed means that those large files will be pulled down to contributors laptop (waste everybody&amp;#8217;s space although up to 2G:);&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With all these implications, shrinking the size of a repo isn&amp;#8217;t as straightforward as just removing large files from current commit. We&amp;#8217;d have to&amp;nbsp;rewrite the commit history. Here are the steps we should take once repo size grows over the soft limit.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-clean-up-remote-orphaned-branches"&gt;Clean up remote orphaned branches&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Removing these branches (remotes/origin/branchname) per se does not free up space. It simplifies the branch structure, leaving /remote/origin/HEAD the only branch left to cleanse for the rest of the steps.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# git push origin --delete branchname&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 class="wp-block-heading" id="h-remove-useless-files-in-current-commit-head"&gt;Remove useless files in current commit (HEAD)&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this step we remove useless files in current commit. Again we should not expect much space freed because all file committed previously, even deleted, are still stored. They are just now showing up in the working directory. For this step, we can create a separate local dir on Mac:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p /Users/digihunch/repo-cleanup&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /Users/digihunch/repo-cleanup&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now within the new directory, we create a bare repo and then the full repo:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone --mirror https://gh@bitbucket.org/digihunch/source.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone https://gh@bitbucket.org/digihunch/source.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we dive into the full repo and identify the large files:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;find . -type f -size +1000k -exec ls -lh {} \; |awk &amp;#39;{print $9&amp;#34;:&amp;#34; $5}&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can run &amp;#8220;git rm &amp;#8221; against the files identified as too large or deletable. Then commit and push to remote repo. This removes large files from current commit.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-remove-large-file-and-the-relevant-commits-in-the-history"&gt;Remove large file and the relevant commits in the history&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As previously mentioned, we have to re-write the history so history forget about the large files. After this step, the historical commits that large files are associated with will all be deleted. Compare the two charts below to understand what the effect is:&lt;/p&gt;&#10;&lt;figure class="wp-block-gallery aligncenter has-nested-images columns-default is-cropped wp-block-gallery-3 is-layout-flex wp-block-gallery-is-layout-flex"&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1206" height="1038" data-id="695" src="https://www.digihunch.com/wp-content/uploads/2020/03/git1.png" alt="" class="wp-image-695"/&gt;&lt;/figure&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1234" height="852" data-id="700" src="https://www.digihunch.com/wp-content/uploads/2020/03/git2-1.png" alt="" class="wp-image-700"/&gt;&lt;/figure&gt;&#10;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can use the bare repo created in the last step, with &amp;#8220;git filter-branch&amp;#8221; tool to cleanse the branch tree. Some advocate as a faster third party tool&amp;nbsp;&lt;a href="https://rtyley.github.io/bfg-repo-cleaner/"&gt;BFG Repo-Cleaner&lt;/a&gt;&amp;nbsp;as a faster, third-party alternative but I usually lean towards native tool.&amp;nbsp;This&amp;nbsp;&lt;a href="https://www.nicoespeon.com/en/2014/04/clean-git-repo-like-a-boss/"&gt;article&lt;/a&gt;&amp;nbsp;explains the command switches.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git filter-branch -f --tree-filter &amp;#34;rm -rf \large_file.zip&amp;#34; --prune-empty -- --all&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After this steps the repo should be cleansed. According to this&amp;nbsp;&lt;a href="https://confluence.atlassian.com/bitbucket/reduce-repository-size-321848262.html"&gt;guide&lt;/a&gt;&amp;nbsp;from BitBucket, we still need to contact their support to run a garbage collection for us in order to see the size change. It even takes time for the size to be reflected after garbage collection. This&amp;nbsp;&lt;a href="https://www.saschawillems.de/blog/2017/09/10/how-to-shrink-down-a-github-repository/"&gt;reference&lt;/a&gt;&amp;nbsp;also does great job explaining what we need to do.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-other-contributors-re-sync-history"&gt;Other contributors re-sync history&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is important to understand that the step above modifies history. Although the commit hash did not change, they are assigned with different commit-ids and you can tell from the commit history where it displays former commit id.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This activity only affects remote repository. Each contributor&amp;#8217;s local repository still stores the old commits and should be sync&amp;#8217;ed with the remote origin by deleting the entire repo and run &amp;#8220;git clone&amp;#8221; again. Although not welcomed by every individual contributors, but it is a necessary evil and better approached with explicit instruction.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Because this activity takes higher risks, changes each commit, involves vendor support and requires activities by each contributor, the support team should focus on preventing this from happening instead of fixing it.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-configure-pre-commit-hook-as-a-preventive-measure"&gt;Configure pre-commit hook as a preventive measure&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As we have more Ansible tasks related, working directory becomes complicated and sometimes contributors accidentally committed large unwanted files (and pushed into the remote repo).&amp;nbsp;Down the road, the best practice is to prevent contributors from committing junks.&amp;nbsp;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The best spot to detect this should be a pre-receive hook on the server side, which is only available with self-hosted Bitbucket Server. Unfortunately, this is not a viable option for&amp;nbsp;&lt;a href="https://community.atlassian.com/t5/Bitbucket-questions/Does-Bitbucket-Cloud-support-git-pre-receive-hooks/qaq-p/950235"&gt;Bitbucket cloud&lt;/a&gt;. Our best bet is client-side pre-commit hook, in which a script&amp;nbsp;performs size check when contributors run &amp;#8220;git commit&amp;#8221;. The purpose is to fail the commit if total file size is over the limit (20M), and the hook itself should be version controlled as well. Compared to (server side) pre-receive hook, the drawback of (client side) pre-commit hook is it requires initial client configuration. The upside is it captures large files before commit.&amp;nbsp;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This hook can be a shell script as simple as this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;commitsizelimit&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;stagedfilelist&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;git diff --name-only --cached&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;stagedfilecnt&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$stagedfilelist&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;| sed &lt;span style="color:#e6db74"&gt;&amp;#39;/^\s*$/d&amp;#39;&lt;/span&gt; |wc -l&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[[&lt;/span&gt; $stagedfilecnt -gt &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;]]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; totalcommitsize&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;du -cm $stagedfilelist | tail -1 | cut -f 1&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Redirect output to stderr.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exec 1&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[[&lt;/span&gt; $totalcommitsize &amp;gt; $commitsizelimit &lt;span style="color:#f92672"&gt;]]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Warning: Total size of all files in staging area is &amp;#34;&lt;/span&gt;$totalcommitsize&lt;span style="color:#e6db74"&gt;&amp;#34;MB, exceeding the limit of &amp;#34;&lt;/span&gt;$commitsizelimit&lt;span style="color:#e6db74"&gt;&amp;#34;MB.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34; To list files by size, run &amp;#39;du -ch \$(git diff --name-only --cached)&amp;#39;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34; To drop large ones from staging area with &amp;#39;git rm -f filename&amp;#39;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34; To bypass this limit, use &amp;#39;git commit --no-verify&amp;#39;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exit &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In the repo we will have a .githook directory to store hooks (e.g. ~/source/.githooks/pre-commit) and point to the hooks directory using the following command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git config core.hooksPath .githooks&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2019/10/storage-nitty-gritty-4-of-5-backup-and-archive-solutions/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Storage Nitty-Gritty 4 of 5 – Backup and Archive Solutions&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2019/11/networking-basics-layer-1-and-layer-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Networking Basics 1 of 3 – Layer 1 through Layer 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Git Explained 2 of 2</title><link>https://www.digihunch.com/2019/06/git-explained-2-of-2/</link><pubDate>Tue, 18 Jun 2019 17:06:00 -0400</pubDate><guid>https://www.digihunch.com/2019/06/git-explained-2-of-2/</guid><description>&lt;p class="wp-block-paragraph"&gt;This is a continuation from &lt;a href="https://www.digihunch.com/2019/06/git-explained-1-of-2/"&gt;Git Explained 1 of 2 &lt;/a&gt;where the fundamental concepts are covered. In this article we introduce some tools for customization and maintenance.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As for Git configuration, there are two files to dictate your Git configuration. ~/.gitconfig and .git/config in project directory. Running `git config &amp;#8211;list &amp;#8211;show-origin` shows all config entries and where they are from. For example you can custom your Git hooks location. Neither of the two files are being version controlled, so the configuration is only effective in the client environment,&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;&lt;strong&gt;Server side Git hooks&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Git implementation supports server side hooks (pre-receive, update, post-receive). They are bash scripts placed in .git/hooks with specific names, fired upon event occurrence. Exit code of 1 from the scripts fails the event. Since server side Git hook consumes server resources, many repository hosting vendors (e.g. &lt;a href="https://community.atlassian.com/t5/Bitbucket-questions/Does-Bitbucket-Cloud-support-git-pre-receive-hooks/qaq-p/950235"&gt;BitBucket Cloud&lt;/a&gt;) do not support it. You will need to enable it in self-hosted servers (e.g. BitBucketServer).&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;&lt;strong&gt;Client side Git hooks&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since server side hooks are not widely supported in every vendor, client-side Git hooks is good alternative places to implement functions such as code style check, commit size check, etc&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The default directory for hooks is .git/hooks/ under the project directory is not version controlled and not easy to share with the team. If the hook needs to be shared among project contributors, we can place hooks files in .githooks/ under project directory. This will make the hook files version controlled. In addition, we need to point the hooks to this directory in configuration, by running `git config core.hooksPath .githooks` from project directory.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img loading="lazy" decoding="async" width="1020" height="767" src="https://www.digihunch.com/wp-content/uploads/2019/10/git-hooks.png" alt="" class="wp-image-211"/&gt;&lt;/figure&gt;&#10;&lt;h3 class="wp-block-heading"&gt;&lt;strong&gt;Web hooks &lt;/strong&gt;&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Web hooks can be thought of as an event notification mechanism. It is a common feature provided by VCS repository hosting providers. If a certain type of event occurs to the repo, web hook will fire an RESTful API call. The HTTP Endpoint, authentication secret and event payload are pre-configured in the repo settings. Web hooks are commonly supported by Git-based repo implementation, such as &lt;a href="https://confluence.atlassian.com/bitbucket/manage-webhooks-735643732.html"&gt;BitBucket cloud&lt;/a&gt;, &lt;a href="https://developer.github.com/webhooks/"&gt;GitHub&lt;/a&gt;, &lt;a href="https://docs.gitlab.com/ee/user/project/integrations/webhooks.html"&gt;GitLab&lt;/a&gt; or &lt;a href="https://docs.aws.amazon.com/codecommit/latest/userguide/how-to-notify.html"&gt;AWS CodeCommit&lt;/a&gt;. It can also be enabled in self hosted Git repo. Web hook is a powerful tool to drive downstream event, such as Jenkins to start building the code. The major difference between server side hook and web hook is web hook is RESTful API driven, whereas server side hook is executing a script.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-pipelines"&gt;&lt;strong&gt;Pipelines&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Some repo hosting vendor also provide a feature named pipeline. BitBucket has &lt;a href="https://confluence.atlassian.com/bitbucket/get-started-with-bitbucket-pipelines-792298921.html"&gt;Pipeline&lt;/a&gt; as a CI/CD tool, AWS has &lt;a href="https://aws.amazon.com/codepipeline/"&gt;AWS Code Pipeline&lt;/a&gt;, and GitLab offers &lt;a href="https://docs.gitlab.com/ee/ci/pipelines/"&gt;CI/CD pipeline&lt;/a&gt; as well. These pipelines are usually in the form of a YAML file in the repo with a special name. The YAML spell out the steps to perform along the pipeline.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;&lt;strong&gt;Squash&lt;/strong&gt; Commits&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For small projects I was in the habit of committing to main branch. I often need to squash a number of commits into one to &amp;#8220;clean up&amp;#8221;. Usually a Pull Request (e.g. in GitHub) or Merge Request (e.g. in GitLab) have such option during approval. We can squash a few commits with git command as well (suppose we want to squash the most recent 16 commits):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git rebase -i HEAD~16&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git push origin +main&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command will open text editor to allow you to mark what to do with each commit. You can mark all except one commit as squash. For the commit to keep, mark it as pick. Then save the text editor. Git rebase will perform the squash for you. However, since this is a rebase, do not do this if there are other collaborators working on the same branch.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;&lt;strong&gt;Cleanse a repository&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Take BitBucket cloud for example, the size of a remote repo has a &lt;a href="https://confluence.atlassian.com/bitbucket/what-kind-of-limits-do-you-have-on-repository-file-size-273877699.html"&gt;non-expandable hard limit&lt;/a&gt; of 2GB, and a soft limit of 1GB. Once the soft limit is reached, a warning will be displayed on Bitbucket&amp;#8217;s landing page as well as when contributors pushes changes. Once the hard limit is reached, the entire repo will turn read-only mode.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Space consumption can be caused by accidental committing of large file. As covered in the &lt;a href="https://www.digihunch.com/2019/06/git-explained-1-of-2/"&gt;previous post&lt;/a&gt;, files are stored as blob objects in .git directory. If a file was deleted by `git rm` command, it simply means it is de-referenced from the next commit and on. After all, Git as a distributed version control system, has the ability to magically restore the deleted file when we want. The cost of that magic, is that deleted file is permanently stored in the repo, in the form of blob object, although not present in the working directory. It consumes space not only in remote repo, but also in the local repo of each contributor.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The purpose of Git repository is to store source code which are fairly small texts. However if a contributor pushed in large files, it can be tricky to cleanse a Git repo on the remote side. Here is some guidelines:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt; Before cleansing, identify large unwanted files in current working directory, delete them with `git rm` and then commit this change in master branch. &lt;/li&gt;&#10;&lt;li&gt;Consolidate branches (e.g. delete useless remote branches with `git push origin &amp;#8211;delete branch_name`). This step itself does not free up space in remote repo but it simplifies the branches;&lt;/li&gt;&#10;&lt;li&gt;Remove large blob objects and commit objects that reference them. This step essentially is re-writing the commit history of repo. Given the risk, it is recommended to perform this step from a separate local project directory with bare repo only without working directory. &lt;a href="https://www.nicoespeon.com/en/2014/04/clean-git-repo-like-a-boss/"&gt;This article&lt;/a&gt; presents some good advices and illustrations. The command recommended is `git filter-branch -f &amp;#8211;tree-filter &amp;#8220;rm -rf \large_file.zip&amp;#8221; &amp;#8211;prune-empty &amp;#8212; &amp;#8211;all` ; and if that is too slow &lt;a href="https://rtyley.github.io/bfg-repo-cleaner/"&gt;here&lt;/a&gt; is an alternative named BFG Repo-Cleaner.&lt;/li&gt;&#10;&lt;li&gt;After this step, each commit will have a new hash id. For the repo size to reduce, either wait until the next garbage collection on the server side, or request cloud vendor to run a manual garbage collection.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2019/06/git-explained-1-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Git Explained 1 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2019/07/practical-cryptography-for-it-professional/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Cryptography Basics 1 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Git Explained 1 of 2</title><link>https://www.digihunch.com/2019/06/git-explained-1-of-2/</link><pubDate>Sat, 08 Jun 2019 22:39:00 -0400</pubDate><guid>https://www.digihunch.com/2019/06/git-explained-1-of-2/</guid><description>&lt;p class="wp-block-paragraph"&gt;In a nutshell, Git is a distributed version control system, commonly used as source control management. It places files in one of three logical areas (working dir, staging, repo) below depending where it is in its lifecycle. &lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img loading="lazy" decoding="async" width="1618" height="992" src="https://www.digihunch.com/wp-content/uploads/2019/10/image.png" alt="" class="wp-image-178"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are many cheetsheets out there but this article will just sort through some concepts unique to Git. To understand how Git works it is crucial to think in terms of Git data model.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Working directory&lt;/strong&gt; a single checkout of one version of the project. These files are pulled out of the object database in the Git directory (upon checkout) and placed in the project directory on disk, for you to use or modify;&amp;nbsp;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Index&lt;/strong&gt; a file contained in your Git directory (stored as binary data in file .git/index) that keeps information about what will go into your next commit.&amp;nbsp;To display what&amp;#8217;s in the index, run `git ls-files &amp;#8211;stage`. Read &lt;a href="https://hackernoon.com/https-medium-com-zspajich-understanding-git-data-model-95eb16cc99f5"&gt;this post&lt;/a&gt; for further details&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Repository&lt;/strong&gt;: where Git stores the metadata and &lt;code&gt;&lt;span style="text-decoration: underline;"&gt;object database&lt;/span&gt;&lt;/code&gt; for your project.&amp;nbsp;The local repository is in .git/ under the project directory.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Add&lt;/strong&gt; &amp;#8211; register one or more modified files to staging area. You may edit several files with only a few needed registered for future commit. Add activity ensures the file edited are recorded in the index (as a preview of next commit). You technically need to run add against each file. But the command syntax with * or . allows you to capture all edits in the same directory or under.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Commit&lt;/strong&gt; &amp;#8211; persist the staged file edits to the repository (so they are stored in Git object database). A commit represents all the file edits that were staged by add command in previous steps.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Branch&lt;/strong&gt; &amp;#8211; a branch is simply a movable pointer to a commit. Default branch name created by git init is called &amp;#8220;master&amp;#8221;. Other than the name, there is nothing special about master branch. Everytime you commit, the master branch pointer moves forward automatically. Branch pointers are kept in .git/refs directory. Read &lt;a href="https://hackernoon.com/understanding-git-branching-2662f5882f9"&gt;this post&lt;/a&gt; for further details.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;HEAD&lt;/strong&gt; &amp;#8211; the pointer to the current branch reference, which is in turn a pointer to the last commit made on that branch. Git use HEAD pointer to know what branch you&amp;#8217;re currently on. HEAD will be the parent of the upcoming commit.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Tag&lt;/strong&gt; &amp;#8211; an annotated tag contains the SHA of the commit being tagged. Alias of a commit.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Merge&lt;/strong&gt; &amp;#8211; choose current commit of other branch and apply it onto your branch.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Rebase&lt;/strong&gt; &amp;#8211; copy all commits from other branch to your branch. Compared to merge, rebasing forms a cleaner commit history.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Cherrypick&lt;/strong&gt; &amp;#8211; choose a previous commit from other branch and apply it onto your branch.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Stash&lt;/strong&gt; &amp;#8211; &amp;nbsp;temporarily stashes changes you&amp;#8217;ve made to working tree so you can work on something else, and then come back and re-apply them later on.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Reset&lt;/strong&gt; &amp;#8211; at a high level, reset is to revert some operations. After pulling code, developer usually follow three steps: editing-&amp;gt;add-&amp;gt;commit. reset is to reverse these steps, based on different modes. The Pro Git reference has further details on the &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified"&gt;three different modes&lt;/a&gt;:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;soft mode&lt;/strong&gt; (reverse operation of commit): based on what branch HEAD points to, move where that branch points to (e.g. from latest commit, to a different commit several steps up the link);&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;mixed mode&lt;/strong&gt; (default; reverse operation of commit and add) &amp;#8211; in addition to soft mode, also update index;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;hard mode&lt;/strong&gt; (reverse operation of commit, add and file editing)- in addition to mixed mode, also update working directory. Edit on files are discarded.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Git object model&lt;/strong&gt; &amp;#8211; In Git database, files, commits and directories are stored as objects, In Git &lt;a href="https://shafiul.github.io/gitbook/1_the_git_object_model.html"&gt;object model&lt;/a&gt;, there are three types (to tell object type, run `git cat-file -t`). Read &lt;a href="https://hackernoon.com/https-medium-com-zspajich-understanding-git-data-model-95eb16cc99f5"&gt;this post&lt;/a&gt; for further details:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;blob object&lt;/strong&gt; &amp;#8211; stores file data with metadata; use `git show` to examine blob object;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;tree object&lt;/strong&gt; &amp;#8211; represents a directory. It references other tree objects (sub-directories) or blob objects (files under the directory, of a certain version); use `git ls-tree` to examine tree object;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;commit object&lt;/strong&gt; &amp;#8211; represents a commit. It references its parent commit, as well as a tree object that represents the entire project directory. use `git cat-file -p` to inspect commit object;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This &lt;a href="https://git-scm.com/book/en/v2/Git-Internals-Git-Objects"&gt;diagram&lt;/a&gt; from from Git Pro outlines the interactions amongst these types of objects.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://git-scm.com/book/en/v2/images/data-model-3.png" alt="All the reachable objects in your Git directory."/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;For more details&lt;/strong&gt;, the&amp;nbsp;&lt;a href="https://git-scm.com/book/en/v2"&gt;official documentation&lt;/a&gt;&amp;nbsp;is actually the most helpful reference with illustrations. In addition, I find on Hakcermoon three excellent articles with thorough explanation on&amp;nbsp;&lt;a href="https://hackernoon.com/https-medium-com-zspajich-understanding-git-data-model-95eb16cc99f5"&gt;data model&lt;/a&gt;,&amp;nbsp;&lt;a href="https://hackernoon.com/understanding-git-branching-2662f5882f9"&gt;branching&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://hackernoon.com/understanding-git-index-4821a0765cf"&gt;index&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2019/05/automation-with-ansible-a-primer/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Set up automation with Ansible&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2019/06/git-explained-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Git Explained 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>