<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Roman Labunsky</title>
    <description>The latest articles on Forem by Roman Labunsky (@romanlab).</description>
    <link>https://forem.com/romanlab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F150326%2F6f0d6a96-cad6-45b9-a446-b588a998605e.jpeg</url>
      <title>Forem: Roman Labunsky</title>
      <link>https://forem.com/romanlab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/romanlab"/>
    <language>en</language>
    <item>
      <title>How to build a Helm plugin in minutes</title>
      <dc:creator>Roman Labunsky</dc:creator>
      <pubDate>Wed, 16 Jun 2021 13:39:58 +0000</pubDate>
      <link>https://forem.com/datreeio/how-to-build-a-helm-plugin-in-minutes-47i0</link>
      <guid>https://forem.com/datreeio/how-to-build-a-helm-plugin-in-minutes-47i0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://helm.sh/"&gt;Helm&lt;/a&gt; is a great addition to the Kubernetes ecosystem; it simplifies complex Kubernetes manifests by separating them into charts and values. &lt;/p&gt;

&lt;p&gt;Sharing charts has never been easier especially since all the customizable parameters are located separately (values.yaml). The downside of this is that there’s no single place to see the resulting manifest, as it’s usually compiled and installed in a single step - &lt;code&gt;helm install&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Helm alleviates this with a plugin system that allows you to seamlessly integrate with the Helm flow and easily run custom code that’s not part of the Helm core code which, as we’ll see in the next section, can be written in any language, even Bash.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple example of how to create a Helm plugin
&lt;/h2&gt;

&lt;p&gt;Let's build a simple plugin that prints out some useful information and environment variables that Helm provides.&lt;/p&gt;

&lt;p&gt;A helm plugin consists of a single required file - &lt;code&gt;plugin.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That’s the entrypoint for Helm. By using this, Helm knows the settings for your plugin, the command it executes when the plugin is run and hooks for customizing the plugin lifecycle (more on that later).&lt;/p&gt;

&lt;p&gt;This is the only required file for a complete plugin. You can set it up in 3 steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a plugin.yaml with the following content
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the installation command from the directory of the file&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm plugin install .
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Execute the plugin&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm myplugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A complete example of a Helm plugin
&lt;/h2&gt;

&lt;p&gt;The simple plugin example covers the most basic use cases, goals that can be achieved even with an alias. More complex cases require more points of integration with Helm and greater customizability of the plugin’s lifecycle. The following parts will give you all the necessary tools to execute any logic in the Helm flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lifecycle customization - Install, update and delete hooks
&lt;/h3&gt;

&lt;p&gt;Helm provides under-documented (the closest thing I found to documentation was &lt;a href="https://github.com/helm/helm/blob/bf486a25cdc12017c7dac74d1582a8a16acd37ea/pkg/plugin/hooks.go"&gt;here&lt;/a&gt;) capability for hooking into the plugin’s &lt;a href="https://helm.sh/docs/helm/helm_plugin/"&gt;install, update or uninstall commands&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Each hook corresponds to a command and will execute the provided script when invoked. &lt;/p&gt;

&lt;p&gt;Some plugins may require a more complex installation flow: Downloading a binary based on OS architecture, building or compiling code or simply installing system dependencies. The install script is the place to do it. A useful example can be found &lt;a href="https://github.com/datreeio/helm-datree/blob/6e498e5e966f36a38f67e986022c74781da865b1/scripts/install.sh"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Execution integration
&lt;/h3&gt;

&lt;p&gt;There are two ways to specify what will be executed and when:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;command&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;platformCommand&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;It's important to note that Helm has a hierarchy for choosing the correct command:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;platformCommand(os+arch match)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;platformCommand(os only match)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;command&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Helm plugins aren’t executed in a shell, so complex commands must be part of a script that will be executed every time the plugin is invoked. &lt;/p&gt;

&lt;p&gt;A script can be used to run complex logic, handle parameter parsing, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;command: "$HELM_PLUGIN_DIR/scripts/run.sh"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A comprehensive example of a run script can be found &lt;a href="https://github.com/datreeio/helm-datree/blob/6e498e5e966f36a38f67e986022c74781da865b1/scripts/run.sh"&gt;here&lt;/a&gt;. A very useful thing that we can do is to render the chart and then execute logic on the resulting Kubernetes yaml file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm template "${HELM_OPTIONS[@]}" &amp;gt; ${TEMP_MANIFEST_NAME}

$HELM_PLUGIN_DIR/bin/myplugin ${TEMP_MANIFEST_NAME} "${MYPLUGIN_OPTIONS[@]}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A very important flag in &lt;code&gt;plugin.yaml&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ignoreFlags: false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This flag specifies whether or not the command line params are passed to the plugin or not. If the plugin accepts parameters, this should be set to false.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips and caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Helm exposes many &lt;a href="https://helm.sh/docs/topics/plugins/#environment-variables"&gt;environment variables&lt;/a&gt; that can simplify a lot of the complex logic and provides much of the necessary information and context for the plugin’s execution. &lt;/li&gt;
&lt;li&gt;The &lt;code&gt;useTunnel&lt;/code&gt; flag in &lt;code&gt;config.yaml&lt;/code&gt; is deprecated in Helm V3 and is no longer needed&lt;/li&gt;
&lt;li&gt;To easily test the plugin during development, you can install the plugin from the dev directory by running &lt;code&gt;helm plugin install PATH_TO_DIR&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The plugin can then be uninstalled with &lt;code&gt;helm plugin uninstall PLUGIN_NAME&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Helm plugin writing is easy to learn but difficult to master. Lack of documentation makes writing complex plugins an arduous task. &lt;/p&gt;

&lt;p&gt;This article aims to expose some of the lesser known abilities of the Helm plugin system and to provide tools and scaffolding that remove the limitations of the plugin system and allow execution of as complex a business logic as is necessary to extend Helm’s behavior.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>helm</category>
      <category>devops</category>
    </item>
    <item>
      <title>A guide to GitHub Actions using Node.js for Git workflow automation</title>
      <dc:creator>Roman Labunsky</dc:creator>
      <pubDate>Sun, 31 Mar 2019 18:09:26 +0000</pubDate>
      <link>https://forem.com/datreeio/a-guide-to-github-actions-using-node-js-for-git-workflow-automation-43bc</link>
      <guid>https://forem.com/datreeio/a-guide-to-github-actions-using-node-js-for-git-workflow-automation-43bc</guid>
      <description>&lt;h2&gt;
  
  
  What is Github Actions?
&lt;/h2&gt;

&lt;p&gt;GitHub Actions, a feature announced in October 2018 during GitHub Universe, generated immense hype under the apt positioning as the “swiss army knife” of git workflow automation.&lt;/p&gt;

&lt;p&gt;Github Actions allows developers to perform tasks automatically in a Github workflow, such as pushing commits to a repository, deploying a release to staging, running tests, removing feature flags, and so on, by way of a simple text file.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://chewy.com/" rel="noopener noreferrer"&gt;Chewy.com&lt;/a&gt;, for example, demoed an action that checks if a &lt;a href="https://vimeo.com/295656803" rel="noopener noreferrer"&gt;Jira ticket number is included&lt;/a&gt; in every pull request name among other things, to ensure the code being deployed to production is compliant with their policies and best practices.&lt;/p&gt;

&lt;h3&gt;
  
  
  “With GitHub Actions, you can automate your workflow from idea to production.”
&lt;/h3&gt;

&lt;p&gt;– &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub actions page&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Write Github Actions in Node.js
&lt;/h2&gt;

&lt;p&gt;I started tinkering with the feature as soon as I could get access to the private beta. I noticed that most Actions are written in shell script. GitHub itself promoted writing actions in &lt;a href="https://developer.github.com/actions/creating-github-actions/creating-a-new-action/#using-shell-scripts-to-create-actions" rel="noopener noreferrer"&gt;shell script for simple actions&lt;/a&gt;. While I understand the motivation (so you can quickly and easily start writing Actions), I feel that shell scripts are limited in terms of writing full-fledged software.&lt;/p&gt;

&lt;p&gt;Since I generally work in Node.js, I decided to ‘accept the challenge’ and write my first Action in Node. But, after struggling with the specifics of running Node.js actions and understanding the differences between a simple container and the GitHub execution environment, I decided to write this tutorial in the hope that it will help others who prefer to write an action in JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment setup
&lt;/h2&gt;

&lt;p&gt;The basic setup is straightforward – start by following &lt;a href="https://developer.github.com/actions/creating-github-actions/creating-a-new-action/#creating-a-new-github-action" rel="noopener noreferrer"&gt;GitHub’s tutorial&lt;/a&gt; all the way to the &lt;em&gt;entrypoint.sh&lt;/em&gt; section. The main difference up to this point is in the chosen docker image. I suggest using the &lt;a href="https://github.com/nodejs/docker-node/tree/90043cdde5057865b94fec447ce193fb46b69e18#nodealpine" rel="noopener noreferrer"&gt;alpine image&lt;/a&gt;, it’s very lightweight compared to the regular &lt;a href="https://github.com/nodejs/docker-node/tree/90043cdde5057865b94fec447ce193fb46b69e18#nodealpine" rel="noopener noreferrer"&gt;node image&lt;/a&gt;. In any case, I suggest using an LTS variant, currently being node 10.&lt;/p&gt;

&lt;p&gt;A very important tool, one that helped reduce the development cycle from 5 minutes per iteration to mere seconds is &lt;a href="https://github.com/nektos/act" rel="noopener noreferrer"&gt;Act&lt;/a&gt;, a zero-config, easy to use tool to run actions locally. It doesn’t fully replicate the environment (for obvious reasons, it doesn’t provide a GitHub token, more on that later) but it’s close enough to speed up the development process and test your action locally.&lt;/p&gt;

&lt;p&gt;At the end of this step, you should have a Dockerfile that looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Node-specific setup
&lt;/h2&gt;

&lt;p&gt;This is the basic &lt;em&gt;entrypoint.sh&lt;/em&gt; file that will work for a Node.js action:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;I chose &lt;em&gt;npm ci&lt;/em&gt; because it’s the easiest way to make sure you always get the same versions of the packages you want to install. It requires you to have &lt;em&gt;package.json&lt;/em&gt; and &lt;em&gt;package-lock.json&lt;/em&gt; in your project – but that’s a best practice anyway.&lt;/p&gt;

&lt;p&gt;The installation script is in the entry point file and NOT in the Dockerfile (as is usual in classic container use cases) because it makes it much easier to use an npm token and install private packages. All you need to do is add an NPM_TOKEN secret and use it in the entry point file (above &lt;em&gt;npm ci&lt;/em&gt;) by adding:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN&lt;/em&gt;&lt;br&gt;
&lt;em&gt;node script.js $*&lt;/em&gt; runs the script and passes the action args as arguments to the script.&lt;/p&gt;
&lt;h2&gt;
  
  
  Node script tips and basic structure
&lt;/h2&gt;

&lt;p&gt;Over the years, I’ve developed a preferred structure for a node executable (CLI) script. I will share it here but for the purpose of this tutorial, this part is completely optional and at this stage, you’re more than ready to develop your own action in Node.js.&lt;/p&gt;

&lt;p&gt;The script looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The important section is at the bottom: &lt;em&gt;if (require.main === module)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It checks if the file was imported/required or if it’s the entrypoint into the program. This allows reusing the same module both programmatically and as a CLI tool.&lt;/p&gt;

&lt;p&gt;If this is the entrypoint, I would then parse the command line arguments (using &lt;a href="https://github.com/tj/commander.js/" rel="noopener noreferrer"&gt;commander&lt;/a&gt;) passed in from &lt;em&gt;entrypoint.sh&lt;/em&gt;. The arguments were injected into &lt;em&gt;entrypoint.sh&lt;/em&gt; by GitHub from the workflow file through the container (more on that later).&lt;/p&gt;

&lt;p&gt;I then invoke the main function. Since it’s an async function, I handle its return value with a then clause and handle failure with a catch clause.&lt;/p&gt;

&lt;p&gt;It’s also useful to read the event, provided by GitHub, and use it in the script:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;const event = JSON.parse(fs.readFileSync('/github/workflow/event.json', 'utf8'))&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets, Args, and the execution environment
&lt;/h2&gt;

&lt;p&gt;The Actions environment takes some getting used to. Although GitHub provides great tutorials on all things workflow related, I wanted to mention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub provides many &lt;a href="https://developer.github.com/actions/creating-github-actions/accessing-the-runtime-environment/#environment-variables" rel="noopener noreferrer"&gt;environment variables&lt;/a&gt; inside the container running the Action, but most of the information can also be retrieved from the event file (see section above).&lt;/li&gt;
&lt;li&gt;Others are defined in the workflow file, while some are provided as part of the secrets mechanism in GitHub.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://developer.github.com/actions/creating-workflows/storing-secrets/#github-token-secret" rel="noopener noreferrer"&gt;Secrets&lt;/a&gt; are pretty straightforward, you define them in the repo settings tab and then they’re exposed as environment variables inside the container.&lt;/p&gt;

&lt;p&gt;Defining the secret in the repo settings tab:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdatree.staging.wpengine.com%2Fwp-content%2Fuploads%2F2019%2F02%2Fp1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdatree.staging.wpengine.com%2Fwp-content%2Fuploads%2F2019%2F02%2Fp1.png" alt="secrets tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the secret in your workflow file:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdatree.staging.wpengine.com%2Fwp-content%2Fuploads%2F2019%2F02%2Fp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fdatree.staging.wpengine.com%2Fwp-content%2Fuploads%2F2019%2F02%2Fp2.png" alt="secrets code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only exception is the GitHub token, which you don’t need to define in the settings. The token is only exposed in the workflow file and GitHub will provide the token itself with these &lt;a href="https://developer.github.com/actions/creating-workflows/storing-secrets/#github-token-secret" rel="noopener noreferrer"&gt;permissions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another important item to note is the mounted folder GitHub provides. It’s mounted under /github and provides a couple of useful things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the event under &lt;em&gt;/github/workflow/event.json&lt;/em&gt; and&lt;/li&gt;
&lt;li&gt;the repo where the action runs under &lt;em&gt;/github/workspace/REPO_NAME&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More information on the mechanics of the mount can be found &lt;a href="https://developer.github.com/actions/creating-github-actions/creating-a-docker-container/#workdir" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This tutorial provides a good starting point for anyone who wants to create their first Node.js action. My action can be found &lt;a href="https://github.com/marketplace/actions/validate-license-action" rel="noopener noreferrer"&gt;here&lt;/a&gt; and the code for it &lt;a href="https://github.com/datreeio/validate-license-action" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re interested in learning more about how you can use Github Actions to automate git workflows, check out this &lt;a href="https://pages.datree.io/building-a-dev-pipeline-using-github-actions-node.js-and-aws-ecs-fargate" rel="noopener noreferrer"&gt;webinar&lt;/a&gt; to watch how Shimon Tolts, Datree.io Co-founder, &lt;strong&gt;"built a CI/CD dev pipeline with Github Actions, Node.js, Docker, and AWS Fargate"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you have any questions, corrections, or suggestions please comment below or contact me directly.&lt;/p&gt;

</description>
      <category>github</category>
      <category>actions</category>
      <category>devops</category>
      <category>gitops</category>
    </item>
  </channel>
</rss>
