<?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>DEV Community: Mateusz Szostok</title>
    <description>The latest articles on DEV Community by Mateusz Szostok (@mszostok).</description>
    <link>https://dev.to/mszostok</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%2F382158%2Fb4c5a856-7257-43fb-9a52-0c506b28e7be.png</url>
      <title>DEV Community: Mateusz Szostok</title>
      <link>https://dev.to/mszostok</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mszostok"/>
    <language>en</language>
    <item>
      <title>Creating the Botkube Flux Plugin for Day 2 operations</title>
      <dc:creator>Mateusz Szostok</dc:creator>
      <pubDate>Wed, 13 Sep 2023 10:07:30 +0000</pubDate>
      <link>https://dev.to/kubeshop/creating-the-botkube-flux-plugin-for-day-2-operations-9dg</link>
      <guid>https://dev.to/kubeshop/creating-the-botkube-flux-plugin-for-day-2-operations-9dg</guid>
      <description>&lt;p&gt;Hey there, tech enthusiasts! Today we're diving into the world of GitOps with the brand-new Botkube Flux plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In Botkube, we move towards GitOps by developing extensions like the new &lt;a href="https://botkube.io/integration/botkube-flux-kubernetes-integration"&gt;Flux plug-in&lt;/a&gt;. We all know the importance of GitOps workflow automation—it's the backbone of modern Kubernetes management. But sometimes, we encounter situations where automation is needed but hasn't been implemented yet. That's where Botkube steps in.&lt;/p&gt;

&lt;p&gt;The Botkube Flux plugin is designed to bridge the gap between your Kubernetes clusters and GitHub repositories. It enables you to execute commands directly from Slack using interactive forms. Plus, it keeps you in the loop by notifying you about GitHub pull requests that are altering your kustomization files. It provides a dedicated button to show a diff report between the pull request and your current cluster state.&lt;/p&gt;

&lt;p&gt;In this blog post, we will reveal the cards and dive deep into the thought process and implementation details of the Flux plugin. You'll get to know the Zapier-like aspect of Botkube that connects Kubernetes clusters, GitHub, and the Flux CLI – all of this to make you fall in love with your Day 2 operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Evolution of Flux Executor Plugin
&lt;/h2&gt;

&lt;p&gt;Now, let's get into the nitty-gritty of this plugin's journey. Picture yourself as part of a team managing Kubernetes applications with multiple pull requests. Our goal is to integrate with Flux CD to simplify Day 2 operations for Flux users. Let's take a closer look at the user story:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Be able to run Flux CLI commands from any communication platform, just like you do in your terminal:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MKZsAkfB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cuyy3xrn0vr8e4ahm73v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MKZsAkfB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cuyy3xrn0vr8e4ahm73v.png" alt="flux-get-source-git.png" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next, let's make it mobile friendly. The secret ingredient is interactivity like buttons and select menus.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L-SOs4n0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qel9y457dtk3eoewtwlh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L-SOs4n0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qel9y457dtk3eoewtwlh.png" alt="flux-get-source-git-btns.png" width="800" height="383"&gt;&lt;/a&gt;&lt;br&gt;
Typing or copy-pasting a long names doesn't work well. Now, you have a handy Flux client right in your pocket, ready with just a few clicks.&lt;br&gt;
And we are just half-way there 😈&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Here comes the last, but unique, part that makes the difference: &lt;strong&gt;support for Day 2 operations&lt;/strong&gt;. &lt;br&gt;
In our case, we stitched together three important parts: Kubernetes cluster, GitHub platform, and Flux CLI. As a result, we've provided a streamlined experience for generating a diff report in the context of GitHub pull requests and the current cluster state.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sy_UgMeZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zuqz0az0prfmg73c5q9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sy_UgMeZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zuqz0az0prfmg73c5q9.png" alt="flux-diff.png" width="800" height="420"&gt;&lt;/a&gt;&lt;br&gt;
🎁 As you may notice, the diff report notification includes some useful actions out-of-the-box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Posting the diff report as a GitHub comment.&lt;/li&gt;
&lt;li&gt;Approving the pull request.&lt;/li&gt;
&lt;li&gt;Viewing the pull request.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now, when we're happy about the result, we are still missing one more part to &lt;strong&gt;automate our day 2 operation&lt;/strong&gt;.&lt;br&gt;
Even though the diffing flow integrates with GitHub, it still requires two manual steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;discovering that a new pull request was created&lt;/li&gt;
&lt;li&gt;constructing a Flux related command&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's how the GitHub Events source was born. Now we can set up a complete workflow to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Watch for GitHub pull requests that changes files in &lt;code&gt;kustomize&lt;/code&gt; directory. Alternatively, we can use label selectors.&lt;/li&gt;
&lt;li&gt;Get notification on Slack about new pull request.&lt;/li&gt;
&lt;li&gt;Render and embed event-aware button to run a diff report.
  &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gsxXAXf9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1cx5tlsimninzcjjpwok.png" alt="pr-event.png" width="800" height="174"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, you may think that what we achieve in those 4 steps it's great but will be hard to configure. Is it? We hope that included YAML sample proves that it is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flux Executor, with optional GitHub token:
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Events workflow:
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We've kept the syntax generic, allowing you to configure different command buttons under specific types of events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactivity and Decision-Making
&lt;/h2&gt;

&lt;p&gt;Thanks to automated notifications with event-aware buttons, you can easily generate reports and share them with your team or contributors.&lt;/p&gt;

&lt;p&gt;While posting diff reports can be fully automated, you might want to do it intentionally by clicking a button. Why? Because the report may contain sensitive information that you don't want to fully disclose to external contributors.&lt;/p&gt;

&lt;p&gt;Using, for example, GitHUb Action, unfortunately, doesn't give us this freedom. We tried to automate the whole workflow while still keeping you as the decision-maker when it comes to sharing the report or directly approving the PR.&lt;/p&gt;

&lt;p&gt;With that being said, nothing blocks us from adding in the future support for AI assistance. Imagine an AI that reviews the diff report and decides whether to proceed with automated approval. Are you ready for AIOps? Exciting times ahead!&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual Approach vs. Botkube Flux Plugin
&lt;/h2&gt;

&lt;p&gt;While you were reading the first part of the Flux plugin evolution, did you consider what kind of manual steps would be required without the plugin? Let's break it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checking GitHub repository for a new pull requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(One-time operation)&lt;/strong&gt; Downloading and installing Flux CLI on you localhost.&lt;/li&gt;
&lt;li&gt;Manually connecting to the related Kubernetes cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(One-time operation)&lt;/strong&gt; Cloning the repository.&lt;/li&gt;
&lt;li&gt;Checking out the pull request.&lt;/li&gt;
&lt;li&gt;Constructing a Flux command.&lt;/li&gt;
&lt;li&gt;Sharing the diff report on Slack/GitHub.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even if we exclude the one-time operations, we're left with 5 steps for each new pull request. Lots of manual steps mean lots of room for human errors. Plus, all that jumping between different sites and context switching can impact your productivity. It's much better to focus on the main aspect, which is the review, and let automation handle the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the Scenes: Developing the Botkube Flux Plugin
&lt;/h2&gt;

&lt;p&gt;The development of the Botkube Flux Executor plugin involved several key aspects:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;🕹️ &lt;strong&gt;Interactivity&lt;/strong&gt;: We leveraged the &lt;a href="https://docs.botkube.io/usage/executor/exec#table-parser"&gt;&lt;code&gt;exec&lt;/code&gt;&lt;/a&gt; plugin developed in previous release, making adding interactivity almost out-of-the-box. The &lt;code&gt;exec&lt;/code&gt; plugin allows you to port any CLI into the communication platform window.&lt;br&gt;
In this case, we reused it as Go SDK. Here is the blueprint that describes translation of CLI table output into an interactive message:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

This makes the implementation much simpler, so we could focus more on native support for the diffing flow.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🗃️ &lt;strong&gt;Storing blueprints&lt;/strong&gt;: The blueprints used for converting CLI output into interactive messages are not stored directly in the Go code. Instead, we store them as YAML files in a dedicated folder so that IDEs, the GitHub platform, and others can provide us with valid syntax highlighting and formatting.&lt;br&gt;
Next, we use Go embed functionality, which is really handy here. With just three lines of code, Go embeds the entire directory with our manifests:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Thanks to embedding it, we can distribute it as a single plugin binary, and we don't need to make any external download calls on startup.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔍 &amp;amp; 🔐 &lt;strong&gt;Auto-discovering GitHub repos&lt;/strong&gt;: In order to discover related GitHub repository, we need to get Flux custom resource. We used the &lt;a href="https://github.com/kubernetes-sigs/controller-runtime/blob/main/pkg/client/client.go"&gt;&lt;code&gt;controller-runtime&lt;/code&gt;&lt;/a&gt; client, which supports Go types natively. This eliminated the need to work with the unstructured type, making things smoother and less error-prone. This is backed by dedicated plugin &lt;strong&gt;RBAC&lt;/strong&gt; impersonation that we introduced a couple releases ago.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🔄 &lt;strong&gt;Cloning and checking out PR&lt;/strong&gt;: Checking out a pull request can be tricky, especially when dealing with external contributors and their forks. Instead of reinventing the wheel, we integrated the widely-known &lt;code&gt;gh&lt;/code&gt; CLI. It was easy to add an external dependency just by defining:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Under the hood we use &lt;code&gt;go-getter&lt;/code&gt; library which has a lot of great features. If you need to download assets from different sources, we recommend that library your projects as well.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The trickiest part was to develop GitHub Events source. The best way is to use GitHub App with the webhook approach. However, we didn't want to require you to have an external endpoint exposed from your cluster.&lt;/p&gt;

&lt;p&gt;We started with &lt;a href="https://docs.github.com/en/rest/activity/events?apiVersion=2022-11-28#list-repository-events"&gt;GitHub repository events endpoint&lt;/a&gt;. But it turned out that even though it serves events that we are interested in, it was not meant to be used for the real-time use-cases. We still integrate with the &lt;code&gt;events&lt;/code&gt; API, but it's recommended for event subscription where time is not that important. For example, getting notification about new stars on your GitHub repositories:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O0xxylK8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zvpn8dwrotquqvw41s5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O0xxylK8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zvpn8dwrotquqvw41s5j.png" alt="stars" width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To achieve our e2e goal, we decided to develop a custom polling mechanism that uses &lt;a href="https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests"&gt;pull request endpoint&lt;/a&gt;. The polling mechanism forces us to be more rational about the number of calls to fit into defined rate limits. We decided on two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#conditional-requests"&gt;Conditional requests&lt;/a&gt; because receiving a 304 response doesn't count against token rate limit.&lt;/li&gt;
&lt;li&gt;Adding support for GitHub App tokens. By using GitHub Apps, you can increase your maximum rate limits because multiple GitHub Apps are independent and do not share the rate limits. Where, using multiple Personal Access Tokens (PATs) for the same account will result in sharing the same rate limit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the future, we can consider adding a token rotator that automatically switches tokens before hitting the rate limit.&lt;/p&gt;

&lt;p&gt;For the &lt;a href="https://app.botkube.io/"&gt;Botkube web app&lt;/a&gt; we will consider native integration using GitHub App, to reduce friction with the initial setup for Flux and GitHub Events plugins.&lt;/p&gt;

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

&lt;p&gt;The &lt;a href="https://botkube.io/blog/introducing-botkubes-integration-with-flux"&gt;Botkube Flux plug-in&lt;/a&gt; offers valuable solutions for streamlining GitOps workflows. Its capabilities, including mobile-friendly interactivity and automated Day 2 operations support, can significantly enhance your Kubernetes management.&lt;/p&gt;

&lt;p&gt;We encourage you to explore the Botkube Flux plugin and consider integrating it into your workflows. Don't hesitate to share your feedback and ideas about Botkube. Feel free to reach out to us on &lt;a href="https://join.botkube.io/"&gt;Slack&lt;/a&gt; or &lt;a href="http://twitter.com/botkube_io"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for taking the time to learn about Botkube 🙌&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>productivity</category>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>Build a GitHub Issues Reporter for failing Kubernetes Apps with Botkube Plugins</title>
      <dc:creator>Mateusz Szostok</dc:creator>
      <pubDate>Thu, 02 Feb 2023 09:38:50 +0000</pubDate>
      <link>https://dev.to/kubeshop/build-a-github-issues-reporter-for-failing-kubernetes-apps-with-botkube-plugins-5h3b</link>
      <guid>https://dev.to/kubeshop/build-a-github-issues-reporter-for-failing-kubernetes-apps-with-botkube-plugins-5h3b</guid>
      <description>&lt;p&gt;Botkube gives you fast and simple access to your clusters right from your communication platform. It does that by sending &lt;a href="https://docs.botkube.io/usage/event-notifications#actionable-notifications" rel="noopener noreferrer"&gt;actionable notifications&lt;/a&gt; (via &lt;a href="https://docs.botkube.io/architecture/#source" rel="noopener noreferrer"&gt;&lt;em&gt;sources&lt;/em&gt;&lt;/a&gt;) and allowing you to run &lt;code&gt;kubectl&lt;/code&gt; and &lt;code&gt;helm&lt;/code&gt; commands (via &lt;a href="https://docs.botkube.io/architecture/#executor" rel="noopener noreferrer"&gt;&lt;em&gt;executors&lt;/em&gt;&lt;/a&gt;) straight from the platform (Slack, Discord, Microsoft Teams and Mattermost).&lt;/p&gt;

&lt;p&gt;In the recent Botkube 0.17 release, we took it to the next level by giving you an easy way to bring your own tools to a chat window!&lt;/p&gt;

&lt;p&gt;In this blog post, you will learn how to develop your own executor plugin to fill the gap in your daily workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;To make it simple but functional, I will show you how to develop an executor that creates an issue for failing Kubernetes resources such as Job, Deployment, StatefulSet, and Pod.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3vuh6q5rfip2rr77mjn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3vuh6q5rfip2rr77mjn.gif" alt="GitHub executor final demo" width="720" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it's worth it
&lt;/h3&gt;

&lt;p&gt;With just a few lines of code, we will automate the process of creating a GitHub issue that out-of-the box contains Kubernetes-specific information useful for further debugging. All of that, directly from Slack, Discord, Mattermost, or MS Teams! No need for connecting to your cluster in your terminal, installing and running &lt;code&gt;kubectl&lt;/code&gt; commands and copy-pasting fetched details into your browser.&lt;/p&gt;

&lt;p&gt;Instead, you will be able to type &lt;code&gt;@Botkube gh create issue pod/foo&lt;/code&gt; from any device that has access to your chat, including mobile apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Access to a Kubernetes cluster&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip&lt;/strong&gt;&lt;br&gt;
To create a local cluster for testing purposes using &lt;a href="https://k3d.io/v5.0.1/#installation" rel="noopener noreferrer"&gt;&lt;code&gt;k3d&lt;/code&gt;&lt;/a&gt;, run:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;k3d cluster create
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.botkube.io/installation/" rel="noopener noreferrer"&gt;Botkube installed and configured&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Basic understanding of the Go language&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://go.dev/doc/install" rel="noopener noreferrer"&gt;Go&lt;/a&gt; at least 1.18.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's under the hood
&lt;/h2&gt;

&lt;p&gt;To understand better what we will develop, I want to give you a bigger picture of the Botkube plugin system. The below animation focuses only on the executor part, but it's almost the same for sources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgackvlblblnigfflhdaz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgackvlblblnigfflhdaz.gif" alt="Plugin system architecture animation" width="760" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new part is a plugin repository that we &lt;a href="https://botkube.io/blog/botkube-v017-release-notes" rel="noopener noreferrer"&gt;introduced in &lt;code&gt;0.17&lt;/code&gt;&lt;/a&gt;. Plugin repository is a place where you store your plugin binaries and index files. Any static file server can be used, and in this blog post we will use a GitHub release. It’s similar to what you know from the Helm ecosystem.&lt;/p&gt;

&lt;p&gt;The plugin manager consumes user's configuration, and downloads and starts &lt;strong&gt;only&lt;/strong&gt; enabled plugins from a given repository. Plugins are running directly on the Kubernetes cluster where the Botkube core was installed.&lt;/p&gt;

&lt;p&gt;Such approach allows us to decouple the Botkube core and its extensions. Thanks to that, we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid having the Botkube core crash if a given plugin malfunctions&lt;/li&gt;
&lt;li&gt;Write extensions in any language as gRPC is used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the end-user's perspective, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specify and use multiple plugin repositories at the same time&lt;/li&gt;
&lt;li&gt;Enable different plugin versions within the same Botkube version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more, see &lt;a href="https://docs.botkube.io/architecture/" rel="noopener noreferrer"&gt;Botkube architecture&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-By-Step Instructions
&lt;/h2&gt;

&lt;p&gt;To quickly onboard you to Botkube plugins, we maintain the &lt;a href="https://github.com/kubeshop/botkube-plugins-template" rel="noopener noreferrer"&gt;kubeshop/botkube-plugins-template&lt;/a&gt; repository that has all batteries included. Our first step is to bootstrap your own GitHub repository.&lt;/p&gt;

&lt;p&gt;To check out the entire code, visit the &lt;a href="https://github.com/kubeshop/botkube/tree/main/cmd/executor/gh/main.go" rel="noopener noreferrer"&gt;Botkube GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repository setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Navigate to &lt;a href="https://github.com/kubeshop/botkube-plugins-template" rel="noopener noreferrer"&gt;&lt;code&gt;botkube-plugins-template&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Use this template&lt;/strong&gt;, and then &lt;strong&gt;Create a new repository&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwjz6mu98hmae11ku3uw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwjz6mu98hmae11ku3uw.png" alt="Create Repo" width="800" height="137"&gt;&lt;/a&gt;&lt;br&gt;
By doing so, you will create your own plugin repository with a single commit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone your repository locally:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh clone &lt;span class="o"&gt;{&lt;/span&gt;owner&lt;span class="o"&gt;}&lt;/span&gt;/&lt;span class="o"&gt;{&lt;/span&gt;repo_name&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# for example, gh clone mszostok/botkube-plugins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create and push a new tag to perform the initial release:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git tag v0.0.1
git push &lt;span class="nt"&gt;--tags&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;After a few minutes you should see a new GitHub release.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Voilà! You are already an owner of fully functional Botkube plugins. Now it's time to add your own brick by creating a GitHub executor.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip&lt;/strong&gt;&lt;br&gt;
In this blog post, we use only GitHub releases that work out-of-the-box. Releasing plugins on GitHub Pages requires additional setup. To support them too, see the &lt;a href="https://docs.botkube.io/plugin/quick-start#use-template" rel="noopener noreferrer"&gt;use template&lt;/a&gt; document.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Develop GitHub executor
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip&lt;/strong&gt;&lt;br&gt;
To make the code-snippets more readable, I skipped the error handling. However, it will be useful if you will add error handling for the final implementation. You can check the &lt;a href="https://github.com/kubeshop/botkube/tree/main/cmd/executor/gh/main.go" rel="noopener noreferrer"&gt;full &lt;code&gt;gh&lt;/code&gt; source-code&lt;/a&gt; for the reference.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;cmd/gh/main.go&lt;/code&gt; file with the following template:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

This template code imports required packages and registers &lt;code&gt;GHExecutor&lt;/code&gt; as the gRPC plugin. Our &lt;code&gt;GHExecutor&lt;/code&gt; service already implements the required &lt;a href="https://github.com/kubeshop/botkube/blob/main/proto/executor.proto" rel="noopener noreferrer"&gt;Protocol Buffers&lt;/a&gt; contract. As you can see, we require &lt;strong&gt;only 2 methods&lt;/strong&gt;.

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;&lt;code&gt;Metadata&lt;/code&gt;&lt;/strong&gt; method returns basic information about your plugin. This data is used when the plugin index is &lt;a href="https://docs.botkube.io/plugin/repo" rel="noopener noreferrer"&gt;generated in an automated way&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;code&gt;Execute&lt;/code&gt;&lt;/strong&gt; method is the heart of your executor plugin. This method runs your business logic and returns the output as plaintext. Next, the Botkube core sends back the response to a given communication platform.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To download the imported dependencies, in your terminal, run:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go mod tidy
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Great! At this stage you already have a functional Botkube executor plugin. However, for now, it only responds with a hard-coded "Aloha!" greeting. But it can do that already on all supported communication platforms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5g9an2k0ubmzvgqldf5k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5g9an2k0ubmzvgqldf5k.gif" alt="Demo Aloha" width="720" height="229"&gt;&lt;/a&gt;&lt;br&gt;
Don't worry, in the next steps, we will implement our business logic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add support for user configuration:&lt;/p&gt;


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


&lt;p&gt;For each &lt;code&gt;Execute&lt;/code&gt; method call, Botkube attaches the list of associated configurations. The input parameters are defined by the user, when enabling a given plugin:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;executors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plugin-based"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repo-name/gh&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# If not enabled, plugin is not downloaded and started.&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Plugin's specific configuration.&lt;/span&gt;
        &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mszostok/repository"&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github_pat_foo"&lt;/span&gt;
          &lt;span class="na"&gt;issueTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;## Description&lt;/span&gt;

            &lt;span class="s"&gt;This issue refers to the problems connected with {{ .Type | code "bash" }} in namespace {{ .Namespace | code "bash" }}&lt;/span&gt;

            &lt;span class="s"&gt;{{ .Logs | code "bash"}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;In our case, we need to have a GitHub token, GitHub repository where the issue should be created, and an issue template. The remaining parameters can be hard-coded on the plugin side, however, your plugin will be more flexible if you allow your users to change it without rebuilding your plugins.&lt;/p&gt;

&lt;p&gt;It's up to the plugin author to merge the passed configurations. You can use our helper function from the plugin extension package (&lt;code&gt;pluginx&lt;/code&gt;). To learn more, see &lt;a href="https://docs.botkube.io/plugin/custom-executor#passing-configuration-to-your-plugin" rel="noopener noreferrer"&gt;Passing configuration to your plugin&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Let's implement command parsing to properly understand command syntax:&lt;/p&gt;

&lt;p&gt;Our goal is to parse &lt;code&gt;gh create issue KIND/NAME [-n, --namespace]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are a lot of great libraries supporting command parsing. The most popular is probably &lt;a href="https://github.com/spf13/cobra" rel="noopener noreferrer"&gt;&lt;code&gt;cobra&lt;/code&gt;&lt;/a&gt;, but for our use case, we will just use the helper function from our plugin extension package.&lt;/p&gt;


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


&lt;p&gt;Under the hood, the &lt;code&gt;pluginx.ParseCommand&lt;/code&gt; method uses &lt;a href="https://github.com/alexflint/go-arg" rel="noopener noreferrer"&gt;&lt;code&gt;go-arg&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We are almost there! Now let's fetch the issue details:&lt;/p&gt;


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


&lt;p&gt;Here, we fetch logs and the cluster version, but you can extend it to fetch other details about your cluster. To make it as simple as possible, we use the &lt;code&gt;kubectl&lt;/code&gt; CLI and avoid reimplementing a bit more complicated logic for fetching logs from all different Kubernetes kinds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Render issue description:&lt;/p&gt;


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


&lt;p&gt;In this step, we use the issue template that the user specified in plugin configuration. I decided to use the &lt;a href="https://pkg.go.dev/text/template" rel="noopener noreferrer"&gt;Go template&lt;/a&gt;, as it fits perfectly into our template rendering flow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally! Submitting an issue!&lt;/p&gt;


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


&lt;p&gt;GitHub provides a great &lt;a href="https://cli.github.com" rel="noopener noreferrer"&gt;&lt;code&gt;gh&lt;/code&gt;&lt;/a&gt; CLI, which we use to submit our issue. To learn more about the CLI syntax, see their &lt;a href="https://cli.github.com/manual/gh_issue_create" rel="noopener noreferrer"&gt;manual&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;gh&lt;/code&gt; CLI doesn't accept fine-grained access tokens. As a workaround, you can use the &lt;a href="https://gist.github.com/mszostok/defa5a5390e87b4f011b986742f714d8" rel="noopener noreferrer"&gt;Go SDK&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The last part is to download our dependencies.&lt;/p&gt;


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


&lt;p&gt;We already improved this step and in the 0.18 version Botkube will download defined &lt;a href="https://docs.botkube.io/next/plugin/dependencies" rel="noopener noreferrer"&gt;dependencies automatically&lt;/a&gt;. For now, you can use the &lt;code&gt;pluginx.DownloadDependencies&lt;/code&gt; function to call our downloader explicitly. The syntax will remain the same.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Congrats! The &lt;code&gt;gh&lt;/code&gt; plugin is finally implemented. Now, let's play a DevOps role! 😈 In the next section, I will show you how to build and release your brand-new executor plugin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Release the &lt;code&gt;gh&lt;/code&gt; executor
&lt;/h3&gt;

&lt;p&gt;It's time to build your plugin. For that purpose, we will use &lt;a href="https://goreleaser.com/" rel="noopener noreferrer"&gt;GoReleaser&lt;/a&gt;. It simplifies building Go binaries for different architectures. The important thing is to produce the binaries for the architecture of the host platform where Botkube is running. Adjust the &lt;code&gt;goos&lt;/code&gt;, &lt;code&gt;goarch&lt;/code&gt;, and &lt;code&gt;goarm&lt;/code&gt; properties based on this architecture.&lt;/p&gt;

&lt;p&gt;Add new build entry under &lt;code&gt;.goreleaser.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;builds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gh&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cmd/gh/main.go&lt;/span&gt;
    &lt;span class="na"&gt;binary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;executor_gh_{{ .Os }}_{{ .Arch }}&lt;/span&gt;

    &lt;span class="na"&gt;no_unique_dist_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CGO_ENABLED=0&lt;/span&gt;
    &lt;span class="na"&gt;goos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;darwin&lt;/span&gt;
    &lt;span class="na"&gt;goarch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;amd64&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
    &lt;span class="na"&gt;goarm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, we need to distribute our plugins. As we mentioned earlier, a plugin repository can be any static file server. The &lt;a href="https://github.com/kubeshop/botkube-plugins-template" rel="noopener noreferrer"&gt;kubeshop/botkube-plugins-template&lt;/a&gt; repository comes with two &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;a href="https://github.com/kubeshop/botkube-plugins-template/blob/main/.github/workflows/release.yml" rel="noopener noreferrer"&gt;&lt;code&gt;.github/workflows/release.yml&lt;/code&gt;&lt;/a&gt; action, which builds the plugin binaries and index file each time a new tag is pushed.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/kubeshop/botkube-plugins-template/blob/main/.github/workflows/pages-release.yml" rel="noopener noreferrer"&gt;&lt;code&gt;.github/workflows/pages-release.yml&lt;/code&gt;&lt;/a&gt; action, which updates GitHub Pages with plugin binaries and index file each time a new tag is pushed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To cut a new release, you need to commit all your work and tag a new commit:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Implement gh executor"&lt;/span&gt;
git tag v1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, let's push our changes and the new tag:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push
git push &lt;span class="nt"&gt;--tags&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This triggers GitHub Action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Felakzuzl2wvgn3prmsai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Felakzuzl2wvgn3prmsai.png" alt="GitHub action that creates a new release" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this automation does under the hood&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This automation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installs the latest &lt;a href="https://goreleaser.com/" rel="noopener noreferrer"&gt;GoReleaser&lt;/a&gt; tool.&lt;/li&gt;
&lt;li&gt;Builds all plugin binaries defined in the &lt;code&gt;.goreleaser.yaml&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Generates an index file using the &lt;a href="https://docs.botkube.io/plugin/repo#generate-index-file" rel="noopener noreferrer"&gt;Botkube helper tool&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Generates a release description.&lt;/li&gt;
&lt;li&gt;Uses the &lt;a href="https://cli.github.com" rel="noopener noreferrer"&gt;&lt;code&gt;gh&lt;/code&gt;&lt;/a&gt; CLI to create a new GitHub release.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Use the &lt;code&gt;gh&lt;/code&gt; executor
&lt;/h3&gt;

&lt;p&gt;In the description of a new GitHub release, you will see the repository URL that you can use within Botkube.&lt;/p&gt;
&lt;h4&gt;
  
  
  Steps
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Follow one of our &lt;a href="https://docs.botkube.io/installation" rel="noopener noreferrer"&gt;installation guides&lt;/a&gt;. Once you reach the Botkube deployment step, add flags specified in the steps below to &lt;code&gt;helm install&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export required environment variables:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip&lt;/strong&gt;&lt;br&gt;
Follow the official GitHub guide on how to create a &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-personal-access-token-classic" rel="noopener noreferrer"&gt;personal access token&lt;/a&gt;. To be able to create GitHub issues, add the &lt;code&gt;repo&lt;/code&gt; permission.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;REPOSITORY&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;repo&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="c"&gt;# format OWNER/REPO_NAME, e.g. kubeshop/botkube&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;token&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PLUGINS_URL&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;plugin_index_url&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the &lt;code&gt;gh&lt;/code&gt; executor related configuration:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="s1"&gt;'plugins.repositories.botkube-plugins.url'&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PLUGINS_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="s1"&gt;'executors.plugin-based.botkube-plugins/gh.config.github.repository'&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPOSITORY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="s1"&gt;'executors.plugin-based.botkube-plugins/gh.config.github.token'&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Depending on the selected platform, add the &lt;code&gt;plugin-based&lt;/code&gt; executor binding. For Slack, it looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--set&lt;/span&gt; communications.default-group.socketSlack.channels.default.bindings.executors&lt;span class="o"&gt;=[&lt;/span&gt;&lt;span class="s1"&gt;'plugin-based'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;If you follow all the steps above, you will have all the necessary flags allowing you to install Botkube with the &lt;code&gt;gh&lt;/code&gt; executor!&lt;/p&gt;

&lt;p&gt;Here's an example of a full command that you should have constructed for Slack installation:&lt;/p&gt;


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



&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to your communication channel&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On a given channel, run:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;@Botkube list executors
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;It should return information about enabled &lt;code&gt;gh&lt;/code&gt; executor:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j4xaonurm1evk6pbbjx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j4xaonurm1evk6pbbjx.png" alt="List executors" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a failing Job:&lt;/p&gt;


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


&lt;p&gt;After a few second, you should see a new alert on your channel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62ugf0yc4k4ib82aodot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62ugf0yc4k4ib82aodot.png" alt="Alert" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To create a GitHub issue for a given alert, run:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;@Botkube gh create issue job/oops
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Botkube executors are powerful because they can glue together three important parts: Kubernetes clusters, communicators, and tools of your choice. There would be nothing special about it if it wasn't, in fact, unburdening you of those implementation-specific details.&lt;/p&gt;

&lt;p&gt;As you noticed, you can focus purely on your business logic. Without the need to use different chat libraries, know how to establish secure connection, or make your extension available only on specific channels. What's more, not only do you not have to learn it, but you don't have to support it either, as we do it for you.&lt;/p&gt;

&lt;p&gt;Once Botkube is deployed, your extension will be available to you and your teammates in a given channel. There is no need to maintain your local setup. Thanks to that, you can also easily run executors on private clusters.&lt;/p&gt;

&lt;p&gt;Botkube extensions can be used with other Botkube functionality too. It means that you can use them to &lt;a href="https://docs.botkube.io/configuration/action" rel="noopener noreferrer"&gt;create automation&lt;/a&gt;. We will shed more light on that in the next blog post. Stay tuned!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Implement once, access everywhere (Slack, Discord, Mattermost, MS Teams).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How can I get involved?
&lt;/h2&gt;

&lt;p&gt;The implemented plugin is as simple as possible. However, it is a great base for further extension based on your needs; for example: introduce your own Kubernetes annotation to route the notification to a specific repository, add a threshold to create issues only for constantly failing Pods, etc. The possibilities are endless, and we cannot wait to see what kind of great workflows you will create!&lt;/p&gt;

&lt;p&gt;As always, we want to hear your feedback and ideas about Botkube. Help us plan the Botkube roadmap, get the features you’d like implemented.&lt;/p&gt;

&lt;p&gt;There are plenty of options to contact us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/kubeshop/botkube/issues" rel="noopener noreferrer"&gt;GitHub issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://join.botkube.io/" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;or email our Product Leader at &lt;a href="//mailto:blair@kubeshop.io"&gt;blair@kubeshop.io&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for taking the time to learn about Botkube 🙌&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>cryptocurrency</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>CLI version: a collection of handy tips</title>
      <dc:creator>Mateusz Szostok</dc:creator>
      <pubDate>Tue, 27 Sep 2022 12:21:33 +0000</pubDate>
      <link>https://dev.to/mszostok/cli-version-a-collection-of-handy-tips-1nce</link>
      <guid>https://dev.to/mszostok/cli-version-a-collection-of-handy-tips-1nce</guid>
      <description>&lt;p&gt;Edited by: &lt;a href="https://www.linkedin.com/in/maja-szostok/"&gt;Maja Szostok&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Flag or Command?

&lt;ul&gt;
&lt;li&gt;Flags&lt;/li&gt;
&lt;li&gt;Command&lt;/li&gt;
&lt;li&gt;Decision&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;What to collect&lt;/li&gt;
&lt;li&gt;Output options&lt;/li&gt;
&lt;li&gt;
Bells and whistles

&lt;ul&gt;
&lt;li&gt;Upgrade notice&lt;/li&gt;
&lt;li&gt;Version skew warning&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;li&gt;Bonus&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Including version information in CLIs is as obvious as a shower after a 3-day music festival. But have you ever given it a second thought?&lt;/p&gt;

&lt;p&gt;I researched 16 popular CLIs to find out how they approach this, and ultimately to come up with a solution that is both elegant and useful.&lt;/p&gt;

&lt;p&gt;In this article, I present my findings and outline both the good and the bad of each approach.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e2V8NvjP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofw5gps9ew7esixgc0l0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e2V8NvjP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofw5gps9ew7esixgc0l0.gif" alt="Pretty version" width="880" height="532"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;center&gt;source: &lt;a href="https://szostok.io/projects/version/"&gt;version.szostok.io&lt;/a&gt;
&lt;/center&gt;


&lt;h2&gt;
  
  
  Flag or Command?
&lt;/h2&gt;

&lt;p&gt;Before we discuss what we should display, let's talk about how our user can get the CLI version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mJrigM3N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tm00vihl75hf2uz8rvnl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mJrigM3N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tm00vihl75hf2uz8rvnl.gif" alt="flags-or-commands" width="532" height="256"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Flags
&lt;/h3&gt;

&lt;p&gt;Let's take a look at the most popular options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Might be confusing, as &lt;code&gt;-v&lt;/code&gt; is often used to enable the verbose mode. &lt;br&gt; If you decide to use &lt;code&gt;-v&lt;/code&gt; anyway, consider &lt;code&gt;-d, --debug&lt;/code&gt; for more verbose output.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Being a capital letter, it's a better alternative for &lt;code&gt;-v&lt;/code&gt; as it's visually distinct.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Is a self-descriptive alternative, mentioned by &lt;a href="https://clig.dev/#arguments-and-flags"&gt;CLI guidelines&lt;/a&gt; as a common approach found in CLIs.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;However, flags come with one big problem. It's hard to combine them with additional functionality. The most popular ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Printing a help message with more information about the version itself.&lt;/li&gt;
&lt;li&gt;Printing only the version number.&lt;/li&gt;
&lt;li&gt;Printing only the client version when your CLI works with a server, like &lt;code&gt;kubectl&lt;/code&gt; does.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are some options to deal with that, but they have their own flaws:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;cli&amp;gt; -V --short&lt;/code&gt; — it's hard to tell which flags work together&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A combination of function and &lt;code&gt;-version&lt;/code&gt; suffix. For example:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;cli&amp;gt; &lt;span class="nt"&gt;--short-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Unfortunately, this doesn't scale well. See:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;cli&amp;gt; &lt;span class="nt"&gt;--short-client-version&lt;/span&gt;
&amp;lt;cli&amp;gt; &lt;span class="nt"&gt;--short-server-version&lt;/span&gt;
&amp;lt;cli&amp;gt; &lt;span class="nt"&gt;--json-version&lt;/span&gt;
&amp;lt;cli&amp;gt; &lt;span class="nt"&gt;--json-client-version&lt;/span&gt;
&amp;lt;cli&amp;gt; &lt;span class="nt"&gt;--json-server-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Command
&lt;/h3&gt;

&lt;p&gt;An alternative solution is to have a dedicated &lt;code&gt;&amp;lt;cli&amp;gt; version&lt;/code&gt; command, which gives you an easy way to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Print a help message with &lt;code&gt;&amp;lt;cli&amp;gt; version -h&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Combine it with other functionality. For example, &lt;code&gt;--short&lt;/code&gt;, &lt;code&gt;--json&lt;/code&gt;, &lt;code&gt;--client-only&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Introduce related sub-commands. For example, &lt;code&gt;&amp;lt;cli&amp;gt; version check-update&lt;/code&gt; to check if there is a new release.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It requires more typing than &lt;code&gt;&amp;lt;cli&amp;gt; -v&lt;/code&gt;, but you can add aliases, such as &lt;code&gt;&amp;lt;cli&amp;gt; ver&lt;/code&gt; or even &lt;code&gt;&amp;lt;cli&amp;gt; v&lt;/code&gt;. However, this is not something I saw often 🤔.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decision
&lt;/h3&gt;

&lt;p&gt;I guess all of us want to have our cake and eat it too 😎. In this one case, it's possible. Personally, I would suggest having the &lt;code&gt;--version&lt;/code&gt; flag to print the short version, and the &lt;code&gt;&amp;lt;cli&amp;gt; version&lt;/code&gt; command to support more complex use cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RhQcO_jQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/whc7h93donokq75aiuw6.png" alt="bubbles" width="24" height="24"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What to collect
&lt;/h2&gt;

&lt;p&gt;Version information is your CLI's DNA. The data that you collect and display is later useful for the CLI authors and its users. Users want to easily check if they're using the right version. Authors mostly want to find out what revision of the source code was used for a given CLI version, e.g. in case of a bug report.&lt;/p&gt;

&lt;p&gt;Users are the most important part of our projects. Let's start with their needs first!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Get the CLI version — the best option is to use &lt;a href="https://semver.org/"&gt;semantic versioning&lt;/a&gt; here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Get both the client and the server version, if available.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure that you have an option to print the client version only.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Be informed about using an outdated version or &lt;a href="https://www.linkedin.com/pulse/managing-version-skew-len-bass/"&gt;version skew&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get the URL to the GitHub release and/or documentation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For you as the author, displaying these details can come in handy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git commit — the SHA for the commit that a given version was built from.&lt;/li&gt;
&lt;li&gt;Git state — for example, &lt;code&gt;clean&lt;/code&gt; if there are no local code changes when this binary was built; &lt;code&gt;dirty&lt;/code&gt; if the binary was built from locally modified code.&lt;/li&gt;
&lt;li&gt;Build date — the date when the binary was built.&lt;/li&gt;
&lt;li&gt;Programming language version — the version of the language that was used to compile your binary.&lt;/li&gt;
&lt;li&gt;Build user — the creator of the binary. It can be also the name of your automation, e.g. &lt;code&gt;goreleaser-1.11.2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Related components' versions — the versions of the binaries that are used under the hood, e.g. Git, Docker. A good example, of collecting such information is Minikube (&lt;a href="https://github.com/mszostok/cli-analysis#system"&gt;example&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5MVzRxcn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/usxw0ig5u9igzcl64dvt.png" alt="chat" width="24" height="24"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Output options
&lt;/h2&gt;

&lt;p&gt;The final aspect is how to present the data to the user. The most popular option is to print a human-readable message if no flags are specified.&lt;br&gt;
However, for doing version check, options such as short, YAML, and JSON are useful.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;--output=  json | yaml | short&lt;/td&gt;
&lt;td&gt;Displays the version information in the JSON format.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;--client-only&lt;/td&gt;
&lt;td&gt;Displays only the client version.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As a result, on a CI pipeline, we can easily detect a mismatched version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;EXPECTED_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1.42.0"&lt;/span&gt;
&lt;span class="nv"&gt;GOT_VER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;golangci-lint version &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;short 2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOT_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOLANGCI_LINT_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✗ golangci-lint version mismatch, expected &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXPECTED_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, available &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOT_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An unusual finding for me was that Helm CLI supports Go templates. For example, &lt;code&gt;--template='Version: {{.Version}}'&lt;/code&gt; outputs 'Version: v3.2.1'. I personally don't see how it adds value if you already support the JSON and/or YAML output format. Do you find it useful?&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pCyNjxP4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/372jr1b5g87xudhi5g69.png" alt="diamond" width="24" height="24"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Bells and whistles
&lt;/h2&gt;

&lt;p&gt;The below practices stood out as a nice touch for me as a user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upgrade notice
&lt;/h3&gt;

&lt;p&gt;Inform your user when a newer version was released. Additionally, you can detect what option was used for installing your CLI, e.g. &lt;code&gt;brew&lt;/code&gt;, &lt;code&gt;apt&lt;/code&gt;, etc., and suggest a command ready to copy-paste.&lt;/p&gt;

&lt;p&gt;Let me show you a few examples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;GitHub CLI. The upgrade notice is enabled by default. To disable it, you need to export the &lt;code&gt;GH_NO_UPDATE_NOTIFIER&lt;/code&gt; environment variable. It checks for new releases once every 24 hours, and displays an upgrade notice on stderr error if a newer version was found. Here is an example output:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A new release of gh is available: 2.5.1 → v2.5.2
To upgrade, run: brew update &amp;amp;&amp;amp; brew upgrade gh
https://github.com/cli/cli/releases/tag/v2.5.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenFaaS CLI. The upgrade notice is enabled by default. To disable it, you need to specify the &lt;code&gt;--warn-update=false&lt;/code&gt; flag. Here is an example output:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your faas-cli version (0.14.5) may be out of date. Version: 0.14.6 is now available on GitHub.
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Terraform CLI. The upgrade notice is always enabled, however, when the JSON format is used, it only shows the &lt;code&gt;terraform_outdated&lt;/code&gt; property set to &lt;code&gt;true&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform v1.2.8
on darwin_amd64

Your version of Terraform is out of date! The latest version
is 1.2.9. You can update by downloading from https://www.terraform.io/downloads.html
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h3&gt;
  
  
  Version skew warning
&lt;/h3&gt;

&lt;p&gt;Print a warning message if the difference between the CLI and related components is greater than the supported version skew.&lt;/p&gt;

&lt;p&gt;The related components include but are not limited to the server that your CLI connects to, and/or other binaries that are used under the hood, such as Git, Docker, etc.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;You may think: printing CLI version… isn't that obvious? Don't you have better things to do instead of thinking how to implement it? And yet, we can find a lot of different approaches. It bothered me sooooo much that I decided to do this research and structure my knowledge in this area. I hope, that you found it useful, too. &lt;strong&gt;Thanks for reading!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Lu5eM7Bg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fnyg0x82jexb7ivr9ird.png" width="24" height="24"&gt;
&lt;/h2&gt;


&lt;center&gt;Follow &lt;a href="https://twitter.com/m_szostok"&gt;@m_szostok&lt;/a&gt; on Twitter to get the latest news.&lt;/center&gt;


&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;If you use &lt;a href="https://go.dev/"&gt;Go&lt;/a&gt; to create your CLI, you don't need to reinvent the wheel - I got your back! You can use the &lt;a href="https://szostok.io/projects/version"&gt;&lt;code&gt;version&lt;/code&gt;&lt;/a&gt; package, which was made to remove repetitiveness in implementing the version command.&lt;/p&gt;

&lt;p&gt;The details about the CLIs that I analyzed can be found at &lt;a href="https://github.com/mszostok/cli-analysis"&gt;mszostok/cli-analysis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cfrPAroL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2gms6trx0lquko79fnn3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cfrPAroL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2gms6trx0lquko79fnn3.gif" alt="preview" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cli</category>
      <category>programming</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Take Capact for a spin!</title>
      <dc:creator>Mateusz Szostok</dc:creator>
      <pubDate>Mon, 07 Feb 2022 13:59:23 +0000</pubDate>
      <link>https://dev.to/capactio/take-capact-for-a-spin-45j2</link>
      <guid>https://dev.to/capactio/take-capact-for-a-spin-45j2</guid>
      <description>&lt;p&gt;Our first blog post focused strongly on &lt;strong&gt;&lt;em&gt;why&lt;/em&gt;&lt;/strong&gt;—why we created Capact, what's the general concept behind it. Now it's time for showing &lt;strong&gt;&lt;em&gt;how&lt;/em&gt;&lt;/strong&gt;—how you can use it.&lt;/p&gt;

&lt;p&gt;Although, there are many ways to &lt;a href="https://capact.io/docs/getting-started"&gt;get started with Capact&lt;/a&gt;, today we will focus on your perspective as a Capact User.&lt;/p&gt;

&lt;p&gt;The recommended way to try out Capact quickly is to set up a local environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y3MD6_Id--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubo9wgwd6cj3h4n87154.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y3MD6_Id--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubo9wgwd6cj3h4n87154.png" alt="install" width="880" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Detailed instructions are already nicely described in our &lt;a href="https://capact.io/docs/installation/local"&gt;local installation&lt;/a&gt; tutorial. Once you have Capact up and running, we can start!&lt;/p&gt;

&lt;h3&gt;
  
  
  What you will learn
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; For this blog post, we will use Capact CLI. However, the described scenario can also be executed with our &lt;a href="https://capact.io/docs/dashboard-ui/overview"&gt;brand-new UI&lt;/a&gt;! 😎&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In 5 minutes, by running examples from this blog post, you will learn about the following Capact concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://capact.io/docs/terminology#interface"&gt;Interface&lt;/a&gt;—provides an option to abstract Implementations.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://capact.io/docs/terminology#implementation"&gt;Implementation&lt;/a&gt;—defines the actual workflow that is executed.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://capact.io/docs/terminology#attribute"&gt;Attribute&lt;/a&gt;—provides an option to &lt;em&gt;tag&lt;/em&gt; manifests. Today we will use it to select a specific Implementation.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://capact.io/docs/terminology#type"&gt;Type&lt;/a&gt; and &lt;a href="https://capact.io/docs/terminology#typeinstance"&gt;TypeInstance&lt;/a&gt;—provide a combined feature, where Type holds the schema and TypeInstance the &lt;em&gt;instance&lt;/em&gt; for the Type.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://capact.io/docs/terminology#action"&gt;Action&lt;/a&gt;—triggers Engine to render and run a given Interface.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://capact.io/docs/feature/policies/overview"&gt;Policy&lt;/a&gt;—impacts the render process to select desired Implementations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Creating new workflows is not the concern of this article, and it will be covered in future blog posts.&lt;/p&gt;

&lt;p&gt;Here we only briefly describe each part, but I encourage you to take a deeper look at our &lt;a href="https://capact.io/docs/terminology"&gt;Terminology&lt;/a&gt; section later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://capact.io/docs/cli/getting-started#install"&gt;Capact CLI&lt;/a&gt; at least v0.6.0&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://capact.io/docs/installation/"&gt;Capact cluster&lt;/a&gt; at least v0.6.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(Optional)&lt;/strong&gt; &lt;a href="https://kubernetes.iohttps://capact.io/docs/tasks/tools/install-kubectl/"&gt;&lt;code&gt;kubectl&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stedolan.github.io/jq/download/"&gt;&lt;code&gt;jq&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📜 Scenario
&lt;/h3&gt;

&lt;p&gt;The key concepts were described in the &lt;a href="https://capact.io/blog/introducing-capact#capact-concept-in-one-minute"&gt;Introducing Capact&lt;/a&gt; blog post. There, we used Mattermost as an example. It was a real life scenario but at the same time more resource and time-consuming.&lt;/p&gt;

&lt;p&gt;For the purpose of this blog post, we created an extremely simple Hello World example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z3tttFYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/459y50wle6x1looj31hw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z3tttFYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/459y50wle6x1looj31hw.png" alt="greet-manifest" width="880" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two Implementations for the &lt;code&gt;cap.interface.capactio.examples.say&lt;/code&gt; Interface. Depending on the Policy, a different Implementation is selected. By default, Engine selects the Implementation which is first on the list (alphabetical order) and doesn't have any requirements regarding executions, such as requiring AWS credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎬 Camera, Lights, Action!
&lt;/h2&gt;

&lt;p&gt;Action is the entry point for executing any Interface. It allows you to define which Interface should be executed and with which parameters. This is later consumed by Capact Engine and the appropriate workflow is rendered. Later, you can review it and approve it for the execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NnhjNDWC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cfdy6a0d77d0t49y7a19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NnhjNDWC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cfdy6a0d77d0t49y7a19.png" alt="capact-simplified-arch" width="861" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://capact.io/docs/cli/getting-started#first-use"&gt;Make sure that you are logged into local cluster&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;List available Interfaces:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;capact hub interfaces get
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;We are interested in this part:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                           PATH          LATEST REVISION                  IMPLEMENTATIONS
---------------------------------------+-----------------+-----------------------------------------------
# ... trimmed ...
---------------------------------------+-----------------+-----------------------------------------------
cap.interface.capactio.examples.greet        0.1.0             cap.implementation.capactio.examples.greet
---------------------------------------+-----------------+-----------------------------------------------
cap.interface.capactio.examples.say          0.1.0             cap.implementation.capactio.examples.hello
                                                               cap.implementation.capactio.examples.ricky
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new Action:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 A Policy is not needed as we use the default behavior here.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;capact act create &lt;span class="nt"&gt;--name&lt;/span&gt; hello cap.interface.capactio.examples.greet
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wait for the Action to have the &lt;code&gt;READY_TO_RUN&lt;/code&gt; status:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; capact act get hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the status is &lt;code&gt;READY_TO_RUN&lt;/code&gt;, run the Action:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; capact act run hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Watch the progress:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; capact act watch hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(Optional)&lt;/strong&gt; Once the Action is finished, copy the name of the Pod for the &lt;code&gt;print&lt;/code&gt; workflow step (column &lt;code&gt;PODNAME&lt;/code&gt;). To read its logs, run:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl logs &lt;span class="o"&gt;{&lt;/span&gt;PODNAME&lt;span class="o"&gt;}&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; ____________________________
&amp;lt; message: Hello from Capact &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;----------------------------&lt;/span&gt;
    &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="se"&gt;\&lt;/span&gt;
                    &lt;span class="c"&gt;##        .&lt;/span&gt;
              &lt;span class="c"&gt;## ## ##       ==&lt;/span&gt;
           &lt;span class="c"&gt;## ## ## ##      ===&lt;/span&gt;
       /&lt;span class="s2"&gt;""""""""""""""""&lt;/span&gt;___/ &lt;span class="o"&gt;===&lt;/span&gt;
  ~~~ &lt;span class="o"&gt;{&lt;/span&gt;~~ ~~~~ ~~~ ~~~~ ~~ ~ /  &lt;span class="o"&gt;===&lt;/span&gt;- ~~~
       &lt;span class="se"&gt;\_&lt;/span&gt;_____ o          __/
        &lt;span class="se"&gt;\ &lt;/span&gt;   &lt;span class="se"&gt;\ &lt;/span&gt;       __/
          &lt;span class="se"&gt;\_&lt;/span&gt;___&lt;span class="se"&gt;\_&lt;/span&gt;_____/
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Get the Action output (TypeInstance). Copy the &lt;code&gt;id&lt;/code&gt; field value:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; capact act get hello &lt;span class="nt"&gt;-ojson&lt;/span&gt; | jq &lt;span class="s1"&gt;'.Actions[0].output.typeInstances'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;
 &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"08fcaa07-7846-47af-b6a7-7c3818c69656"&lt;/span&gt;,
   &lt;span class="s2"&gt;"typeRef"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="s2"&gt;"path"&lt;/span&gt;: &lt;span class="s2"&gt;"cap.type.capactio.examples.message"&lt;/span&gt;,
     &lt;span class="s2"&gt;"revision"&lt;/span&gt;: &lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;
   &lt;span class="o"&gt;}&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Get the TypeInstance &lt;code&gt;value.message&lt;/code&gt; field value:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; capact ti get &lt;span class="o"&gt;{&lt;/span&gt;ID&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-ojson&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[0].latestResourceVersion.spec.value.message'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Delete the Action:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; capact act delete hello
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;What if you wanted to output a different message, that reminds you about a masterpiece song you can listen to endlessly? With Capact that's really easy ✨&lt;/p&gt;

&lt;p&gt;To choose a different Implementation for the &lt;code&gt;say&lt;/code&gt; Interface, we need to prepare a dedicated Policy. The power of it is that you don't have to change the main workflow. You treat the step as a building block and just swap it for another existing one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZFxH2Kyl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6gsp3de7nhb7w4om3vft.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZFxH2Kyl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6gsp3de7nhb7w4om3vft.gif" alt="Here we go again GIF" width="420" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To test it, create the Action again, and pass the Action Policy.&lt;/p&gt;

&lt;p&gt;First, save the Policy to the &lt;code&gt;/tmp/policy.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; /tmp/policy.yaml&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;interface&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cap.interface.capactio.examples.say&lt;/span&gt;
    &lt;span class="na"&gt;oneOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;implementationConstraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cap.attribute.capactio.examples.be-positive&lt;/span&gt;
            &lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.0&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create the Action with the Policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;capact act create &lt;span class="nt"&gt;--name&lt;/span&gt; hello cap.interface.capactio.examples.greet &lt;span class="nt"&gt;--action-policy-from-file&lt;/span&gt; /tmp/policy.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can repeat steps starting from 4th point—so, once again, wait for the Action to have the &lt;code&gt;READY_TO_RUN&lt;/code&gt; status and run it!&lt;/p&gt;

&lt;p&gt;In the optional step no. 7, if you read the Pod logs, the output will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; __________________________________
&amp;lt; message: Never gonna give you up &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;----------------------------------&lt;/span&gt;
    &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="se"&gt;\&lt;/span&gt;
                    &lt;span class="c"&gt;##        .&lt;/span&gt;
              &lt;span class="c"&gt;## ## ##       ==&lt;/span&gt;
           &lt;span class="c"&gt;## ## ## ##      ===&lt;/span&gt;
       /&lt;span class="s2"&gt;""""""""""""""""&lt;/span&gt;___/ &lt;span class="o"&gt;===&lt;/span&gt;
  ~~~ &lt;span class="o"&gt;{&lt;/span&gt;~~ ~~~~ ~~~ ~~~~ ~~ ~ /  &lt;span class="o"&gt;===&lt;/span&gt;- ~~~
       &lt;span class="se"&gt;\_&lt;/span&gt;_____ o          __/
        &lt;span class="se"&gt;\ &lt;/span&gt;   &lt;span class="se"&gt;\ &lt;/span&gt;       __/
          &lt;span class="se"&gt;\_&lt;/span&gt;___&lt;span class="se"&gt;\_&lt;/span&gt;_____/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note also that the produced TypeInstance from no. 9 is valid against the same JSON Schema but the &lt;strong&gt;message&lt;/strong&gt; was changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is so special about it?
&lt;/h2&gt;

&lt;p&gt;Our example was simplified to a minimum, but you could see that you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;chain different steps together,&lt;/li&gt;
&lt;li&gt;have a unified way of execution,&lt;/li&gt;
&lt;li&gt;built-in Type validation based on JSON Schema,&lt;/li&gt;
&lt;li&gt;and more importantly, write once and swap implementation details with ease.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Capact is also great if you want to chain different tools together and keep the same entry point (UX) but that requires more complex examples. For now, we recommend you to take a look at our other examples described in the &lt;strong&gt;How can I get involved and learn more?&lt;/strong&gt; section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the CLI
&lt;/h2&gt;

&lt;p&gt;As you saw, you can use CLI to browse Hub manifests, create a specific Action for a given Interface and retrieve the output. CLI communicates with our Gateway via GraphQL calls, same as the UI (Capact Dashboard). You can read more in the &lt;a href="https://capact.io/docs/architecture/e2e-architecture"&gt;E2E Architecture&lt;/a&gt; document.&lt;/p&gt;

&lt;p&gt;However, a more important question is, how did it happen?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;1  spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="m"&gt;2&lt;/span&gt;    &lt;span class="c1"&gt;# ... trimmed ...&lt;/span&gt;
&lt;span class="na"&gt;3    imports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;4      - interfaceGroupPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cap.interface.capactio.examples&lt;/span&gt;
&lt;span class="na"&gt;5        alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;examples&lt;/span&gt;
&lt;span class="na"&gt;6        methods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;7          - name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;say&lt;/span&gt;
&lt;span class="s"&gt;8&lt;/span&gt;  
&lt;span class="m"&gt;9&lt;/span&gt;    &lt;span class="c1"&gt;# ... trimmed ...&lt;/span&gt;
&lt;span class="na"&gt;10   steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;11     - - name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get-message&lt;/span&gt;
&lt;span class="na"&gt;12         capact-action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;examples.say&lt;/span&gt; &lt;span class="c1"&gt;# dynamic step, rendered by Engine based on the Policy.&lt;/span&gt;
&lt;span class="na"&gt;13     - - name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;print&lt;/span&gt;
&lt;span class="na"&gt;14         template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;print&lt;/span&gt;
&lt;span class="na"&gt;15         arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;16           artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;17             - name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;message&lt;/span&gt;
&lt;span class="na"&gt;18             from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{steps.get-message.outputs.artifacts.message}}"&lt;/span&gt;
&lt;span class="m"&gt;19&lt;/span&gt;     &lt;span class="c1"&gt;# ... trimmed ...&lt;/span&gt;
&lt;span class="na"&gt;20     - name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;print&lt;/span&gt;
&lt;span class="na"&gt;21       inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;22         artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;23           - name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;message&lt;/span&gt;
&lt;span class="na"&gt;24           path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/tmp/message.yaml&lt;/span&gt;
&lt;span class="na"&gt;25       container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;26         image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/whalesay:latest&lt;/span&gt;
&lt;span class="na"&gt;27         command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;sh&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;-c&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;28         args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cowsay&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/tmp/message.yaml"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet above shows the most important parts of the Implementation manifest. In lines &lt;strong&gt;4-7&lt;/strong&gt;, we declare the import, which is later used in steps. In line &lt;strong&gt;12&lt;/strong&gt;, we use &lt;strong&gt;capact-action&lt;/strong&gt; to make a dynamic step with a given Interface.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Instead of providing the full Interface path, we use &lt;strong&gt;alias&lt;/strong&gt; assigned to the imported Interface.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a result, our Engine and, in particular, Argo Renderer has an option to put there an Implementation that fulfills the specified Interface. Thanks to that, this step is swappable based on the defined Policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can I get involved and learn more?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://capact.io/docs/getting-started"&gt;Getting Started&lt;/a&gt; goes into more detail about how to start with Capact and use its more-advanced features. There are also &lt;a href="https://capact.io/docs/example/rocketchat-installation"&gt;Rocket.Chat&lt;/a&gt; and &lt;a href="https://capact.io/docs/example/mattermost-installation"&gt;Mattermost&lt;/a&gt; tutorials that we keep up-to-date for you to walk you through using Capact in real cases.&lt;/p&gt;

&lt;p&gt;We appreciate any input you have about your experience with Capact!&lt;/p&gt;

&lt;p&gt;There are plenty of options to contact us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/capactio/capact/issues"&gt;GitHub issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/community/slack"&gt;Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Email us at &lt;a href="//mailto:contact@capact.io"&gt;contact@capact.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for taking the time to learn about Capact 🙌&lt;/p&gt;

</description>
      <category>capact</category>
      <category>cloudnative</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
