<?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: Beingana Jim Junior</title>
    <description>The latest articles on Forem by Beingana Jim Junior (@jimjunior).</description>
    <link>https://forem.com/jimjunior</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%2F539256%2F4c93693c-d5ba-4723-aab5-1a6947f451b9.jpg</url>
      <title>Forem: Beingana Jim Junior</title>
      <link>https://forem.com/jimjunior</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jimjunior"/>
    <language>en</language>
    <item>
      <title>Implementing Resource Versioning in Conveyor CI</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Sat, 09 Aug 2025 23:02:51 +0000</pubDate>
      <link>https://forem.com/jimjunior/implementing-resource-versioning-in-conveyor-ci-1pn2</link>
      <guid>https://forem.com/jimjunior/implementing-resource-versioning-in-conveyor-ci-1pn2</guid>
      <description>&lt;p&gt;Resource versioning is a key feature in Ci/CD Platforms. It provides developers a way to track how a pipeline has changed over time, ensuring reproducibility, stability, and traceability of builds. Conveyor CI on the other hand, doesn't have this inbuilt into it. This has therefore been a great downside of it, leading developers who might use it miss out on the above mentioned features.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it is designed in other systems
&lt;/h2&gt;

&lt;p&gt;The design or implementation can vary depending on multiple cases like the purpose, use case, or even the internal design of the system. But at the core, you need to be able to differentiate multiple variations of the same process as it is re-triggered. This means each execution of a CI/CD process has to have a unique Identifier that differentiates it from other executions under that same resource. The most common identifiers or ways in which this differentiation is done include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Versioning (SemVer)&lt;/strong&gt;: This is a famous software versioning scheme that follows the &lt;code&gt;MAJOR.MINOR.PATCH&lt;/code&gt; eg &lt;code&gt;1.2.4&lt;/code&gt;. It is easy to understand and commonly used in CI/CD systems like GitHub Actions, Gitlab CI and Jenkins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit SHA Pinning&lt;/strong&gt;: This is a method commonly used in Git based CI/CD systems, whereby they pin the Git Commit to a version and one major advantage of this is it ensures absolute reproducibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Revision Generations&lt;/strong&gt;: In this case a unique string is attached to a resource version. It doesn't have to have any semantic meaning but has to be unique.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Intended Design in Conveyor CI
&lt;/h2&gt;

&lt;p&gt;Conveyor CI being a minimal State Based system that uses a resource driven architecture, it won't base on any external system to implement versioning rather on system state. This means we are free to implement our own versioning scheme. The goal is to use a scheme that is intuitive on the human side and semantically.&lt;/p&gt;

&lt;p&gt;With this in mind, we can opt for an incremental integer based versioning scheme. Meaning we just keep incrementing a positive integer starting from zero with each new resource version created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;p&gt;The proposed design can seem like a simple and easy concept to implement, but just like all technical problems, choosing the right implementation might not be as straight forward as it seems. We had to explore different ways to implement this.&lt;/p&gt;

&lt;p&gt;A straightforward way might be that each time a resource is updated, a new resource is created with a resource version increased by one and its stored in the database, then a pointer is created so that when a user requests for the current/latest resource version we provide the recent update added.&lt;/p&gt;

&lt;p&gt;But this introduces an expected bottle neck. We are assured to blot the database as resources get more revisions. For example, assuming we have 1000 resources and each resource has 100 revisions. That's automatically one hundred thousand records in the database. And say our system becomes big and he has around one million resources each with 100 revisions. The database becomes huge with 100 million records.&lt;/p&gt;

&lt;p&gt;So we have to then take into consideration our data store and investigate if it's able to handle this form of incrementation. Conveyor CI uses &lt;a href="https://etcd.io/" rel="noopener noreferrer"&gt;etcd&lt;/a&gt;, a key-value store, it is reliable and highly performant. As we investigated further into the architecture of &lt;code&gt;etcd&lt;/code&gt;, we realized that internally &lt;code&gt;etcd&lt;/code&gt; uses &lt;em&gt;Multi-Version Concurrency Control (MVCC)&lt;/em&gt; which allows reads at specific revisions of a record or key. This means for every update to an &lt;code&gt;etcd&lt;/code&gt; key you make, &lt;code&gt;etcd&lt;/code&gt; stores the previous version of that record and you can query or read it later on. So technically, rather than having to create a new record each time we update a resource, we can rely on &lt;code&gt;etcd&lt;/code&gt;’s internal MVCC to store revisions. Another upside to this is that &lt;code&gt;etcd&lt;/code&gt; also uses the incremental revision numbering format so we can also rely on that.&lt;/p&gt;

&lt;p&gt;However, the designers of &lt;code&gt;etcd&lt;/code&gt; also released that storing revisions, introduces database blot as the records keep increasing in number and as they keep being updated. So they designed a concept of compacting whereby the database will periodically delete old revisions and keep only the latest version of a record. But this now becomes a danger to the data integrity of conveyor ci, meaning we wont be able to access old revisions since they will be permanently deleted. Luckily, this feature of compacting automatically can be turned off and compacting can be done manually by a system administrator. So we can utilize this manual compacting to our benefit and design our own custom compact strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compact Strategy
&lt;/h3&gt;

&lt;p&gt;To prevent the &lt;code&gt;etcd&lt;/code&gt; from becoming extremely huge and also capitalize on the compact feature in &lt;code&gt;etcd&lt;/code&gt;. We can introduce a custom compact strategy for systems that have managed to generate an extremely large database and performance is degrading. Our strategy works as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable manual &lt;code&gt;etcd&lt;/code&gt; compacting&lt;/li&gt;
&lt;li&gt;Introduce an External Audit Storage.&lt;/li&gt;
&lt;li&gt;Upon compacting, we store a snapshot of the revisions in the External Audit storage system. Then compact &lt;code&gt;etcd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When a user tries to query extremely old revisions, we can then refer and collect them from the external audit system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures that ou &lt;code&gt;etcd&lt;/code&gt; datastore remains efficient without Key Explosion and avoids the performance degradation and storage inefficiency of millions of custom historical keys. The external Audit storage system in some cases can also work as a backup system.&lt;/p&gt;

&lt;p&gt;The above mentioned implementation is currently the best we have and has actually been proved to be production ready by large systems like kubernetes, and it is what will be patched to Conveyor CI, unless proven otherwise or a better implementation is proposed.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>devops</category>
      <category>cloud</category>
      <category>cicd</category>
    </item>
    <item>
      <title>A Complete Guide to etcd: The Distributed Key-Value Store Powering Cloud Infrastructure</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Fri, 25 Jul 2025 21:41:22 +0000</pubDate>
      <link>https://forem.com/jimjunior/a-complete-guide-to-etcd-the-distributed-key-value-store-powering-cloud-infrastructure-pif</link>
      <guid>https://forem.com/jimjunior/a-complete-guide-to-etcd-the-distributed-key-value-store-powering-cloud-infrastructure-pif</guid>
      <description>&lt;p&gt;&lt;a href="https://etcd.io/" rel="noopener noreferrer"&gt;etcd&lt;/a&gt; is a distributed key-value store designed for reliability, high availability, and consistency. It is a &lt;a href="https://www.cncf.io/" rel="noopener noreferrer"&gt;Cloud Native project&lt;/a&gt; that powers much of the tools utilized in the Cloud Ecosystem today. It is used projects like Kubernetes, CoreOS, OpenShift, Cloud Foundry any many more. It is primarily used to store configuration, state and metadata in Cloud Systems. This article will take you through a complete guide to understanding etcd to the point of being able to integrate it into your project. We shall cover how to install it, setup a cluster, store and query etcd, operating and managing etcd and many more concepts. By the end of this blog article, you will have a solid understanding of etcd.&lt;/p&gt;

&lt;p&gt;This article is accompanied with a GitHub repository containing reference code and config files. I would recommend cloning the repository so you can use it to follow along. You can find the code on this repo &lt;a href="https://github.com/jim-junior/etcd-guide.git" rel="noopener noreferrer"&gt;https://github.com/jim-junior/etcd-guide.git&lt;/a&gt;. That being said, this is a long article so let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;To use &lt;code&gt;etcd&lt;/code&gt; you will need to first install it on your system or have an install of it running somewhere. There are multiple ways to install etcd and each method can vary depending on your use-case. This tutorial will try to provide an exhaustive list depending on common use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing on a host machine
&lt;/h3&gt;

&lt;p&gt;This is one of the most common ways you will install software if you want to use it on your workspace. When installing etcd, it comes with two important binaries, the &lt;code&gt;etcd&lt;/code&gt; server binary and &lt;code&gt;etcdctl&lt;/code&gt; CLI tool for interacting with your etcd cluster or instance.&lt;/p&gt;

&lt;p&gt;To install these tools you can head over to the &lt;a href="https://github.com/etcd-io/etcd/releases/" rel="noopener noreferrer"&gt;Github Realeses page&lt;/a&gt; and download the archive for your platform OR&lt;/p&gt;

&lt;h4&gt;
  
  
  For Linux &amp;amp; MacOS
&lt;/h4&gt;

&lt;p&gt;To install on linux, In the Github repository, I provide a custom installation script that will install the latest version for you. Just clone and run the &lt;a href="https://github.com/jim-junior/etcd-guide/blob/main/etcd-install.sh" rel="noopener noreferrer"&gt;&lt;code&gt;etcd-install.sh&lt;/code&gt;&lt;/a&gt; file&lt;/p&gt;

&lt;p&gt;You can also install it via Homebrew if you have it installed on your system via &lt;code&gt;brew install etcd&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  For Windows
&lt;/h4&gt;

&lt;p&gt;To install on WIndows, In the Github repository, I also provide a custom Powershell installation script that will install the latest version for you. Just clone and run the &lt;a href="https://github.com/jim-junior/etcd-guide/blob/main/Install-Etcd.ps1" rel="noopener noreferrer"&gt;&lt;code&gt;Install-Etcd.ps1&lt;/code&gt;&lt;/a&gt; file&lt;/p&gt;

&lt;p&gt;Once you have &lt;code&gt;etcd&lt;/code&gt; installed, you can verify by running the etcd command:&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;$ &lt;/span&gt;etcd
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"warn"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:&lt;span class="s2"&gt;"2025-07-24T16:58:58.804311+0300"&lt;/span&gt;,&lt;span class="s2"&gt;"caller"&lt;/span&gt;:&lt;span class="s2"&gt;"embed/config.go:1209"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"Running http and grpc server on single port. This is not recommended for production."&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installing as a Container
&lt;/h3&gt;

&lt;p&gt;You can also install and run etcd via Docker. The &lt;code&gt;etcd&lt;/code&gt; team provides an official container image that can be downloaded from &lt;code&gt;quay.io/coreos/etcd&lt;/code&gt;. However, there is another image provided by bitnami and you can pull it from the &lt;code&gt;bitnami/etcd&lt;/code&gt; repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run  &lt;span class="nt"&gt;-p&lt;/span&gt; 4001:4001 &lt;span class="nt"&gt;-p&lt;/span&gt; 2380:2380 &lt;span class="nt"&gt;-p&lt;/span&gt; 2379:2379 &lt;span class="nt"&gt;--name&lt;/span&gt; etcd quay.io/coreos/etcd:v2.3.8
&lt;span class="c"&gt;# OR&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 4001:4001 &lt;span class="nt"&gt;-p&lt;/span&gt; 2380:2380 &lt;span class="nt"&gt;-p&lt;/span&gt; 2379:2379 &lt;span class="nt"&gt;--name&lt;/span&gt; etcd bitnami/etcd:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using Kubernetes, you can also Install and run it on Kubernetes as a Statefull set, follow the &lt;a href="https://etcd.io/docs/v3.6/op-guide/kubernetes/" rel="noopener noreferrer"&gt;official Tutorial&lt;/a&gt; on the &lt;code&gt;etcd&lt;/code&gt; documentation&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up an etcd cluster
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, &lt;code&gt;etcd&lt;/code&gt; is a distributed KV store. So by nature it can operate in a cluster whereby you have multiple instances running on different nodes. This makes &lt;code&gt;etcd&lt;/code&gt; highly fault tolerant. This distributed nature is powered by &lt;a href="https://raft.github.io/" rel="noopener noreferrer"&gt;The Raft Consensus Algorithm&lt;/a&gt; and the &lt;code&gt;etcd&lt;/code&gt; team actually provides a really good &lt;a href="https://github.com/etcd-io/raft" rel="noopener noreferrer"&gt;Raft golang implementation&lt;/a&gt;. To set up a cluster you need around 3 to 5 instances for high availability and quorum.&lt;/p&gt;

&lt;p&gt;Let's go through how you can set up an &lt;code&gt;etcd&lt;/code&gt; cluster.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;etcd&lt;/code&gt; cluster is composed of multiple instances running and each instances requires an few requirements which include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: A string to identify each instance and it must be unique across instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peer URL&lt;/strong&gt;: This is a URL used by peer instances to communicate among each other in the cluster. (Usually listens on Port &lt;code&gt;2380&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client URL&lt;/strong&gt;: This is a URL used by external services to interact with your &lt;code&gt;etcd&lt;/code&gt; instance. (Usually listens on Port &lt;code&gt;2379&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that, your cluster also has a few requirements and these are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cluster string&lt;/strong&gt;: This is a string of Comma-separated list of all cluster members in the format of &lt;code&gt;name=peerURL&lt;/code&gt;. eg &lt;code&gt;node1=10.10.10.0:2380,node2=10.10.10.0:2380&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster Token&lt;/strong&gt;: This is a unique string to identify the cluster. It prevents instances from joining the wrong cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lets show an example of how to run a simple cluster. We shall run a cluster with 3 nodes having the following configuration:&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;IP&lt;/th&gt;
&lt;th&gt;Peer URL&lt;/th&gt;
&lt;th&gt;Client URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;etcd-1&lt;/td&gt;
&lt;td&gt;10.0.0.1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://10.0.0.1:2380" rel="noopener noreferrer"&gt;https://10.0.0.1:2380&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://10.0.0.1:2379" rel="noopener noreferrer"&gt;https://10.0.0.1:2379&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;etcd-2&lt;/td&gt;
&lt;td&gt;10.0.0.2&lt;/td&gt;
&lt;td&gt;&lt;a href="https://10.0.0.2:2380" rel="noopener noreferrer"&gt;https://10.0.0.2:2380&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://10.0.0.2:2379" rel="noopener noreferrer"&gt;https://10.0.0.2:2379&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;etcd-3&lt;/td&gt;
&lt;td&gt;10.0.0.3&lt;/td&gt;
&lt;td&gt;&lt;a href="https://10.0.0.3:2380" rel="noopener noreferrer"&gt;https://10.0.0.3:2380&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://10.0.0.3:2379" rel="noopener noreferrer"&gt;https://10.0.0.3:2379&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With the above instance settings, we can extract a cluster string and also generate a custom cluster token.&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="c"&gt;# Cluster String&lt;/span&gt;
etcd-1&lt;span class="o"&gt;=&lt;/span&gt;https://10.0.0.1:2380,etcd-2&lt;span class="o"&gt;=&lt;/span&gt;https://10.0.0.2:2380,etcd-3&lt;span class="o"&gt;=&lt;/span&gt;https://10.0.0.3:2380


&lt;span class="c"&gt;# Cluster token&lt;/span&gt;
my-cluster-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once last thing is that when you are initiating a cluster, there is another configuration you pass to &lt;code&gt;etcd&lt;/code&gt; called the cluster initial state, this can either be &lt;code&gt;new&lt;/code&gt; or &lt;code&gt;existing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now since we have all the major configurations, we can then initiate our cluster by running each instance.&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;$ &lt;/span&gt;etcd &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--name&lt;/span&gt; etcd-1 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--initial-advertise-peer-urls&lt;/span&gt; https://10.0.0.1:2380 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--listen-peer-urls&lt;/span&gt; https://10.0.0.1:2380 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--listen-client-urls&lt;/span&gt; https://10.0.0.1:2379,http://127.0.0.1:2379 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--advertise-client-urls&lt;/span&gt; https://10.0.0.1:2379 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--initial-cluster-token&lt;/span&gt; etcd-cluster-1 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--initial-cluster&lt;/span&gt; etcd-1&lt;span class="o"&gt;=&lt;/span&gt;https://10.0.0.1:2380,etcd-2&lt;span class="o"&gt;=&lt;/span&gt;https://10.0.0.2:2380,etcd-3&lt;span class="o"&gt;=&lt;/span&gt;https://10.0.0.3:2380 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--initial-cluster-state&lt;/span&gt; new &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--data-dir&lt;/span&gt; /var/lib/etcd &lt;span class="se"&gt;\&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run the same command another 2 times but adjusting the flags to match the settings of the other instances. Once that's done. You now have a running cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with etcd
&lt;/h2&gt;

&lt;p&gt;Now that we have installed &lt;code&gt;etcd&lt;/code&gt;. We can move on to working storing and querying &lt;code&gt;etcd&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Interacting with &lt;code&gt;etcd&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When you install &lt;code&gt;etcd&lt;/code&gt;, it comes with another binary called &lt;code&gt;etcdctl&lt;/code&gt;. This is a CLI tool that helps you interact with the &lt;code&gt;etcd&lt;/code&gt; API Server. However, there are other ways to interact with &lt;code&gt;etcd&lt;/code&gt; and all include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;etcdctl&lt;/code&gt;: The official command line tool for interacting with &lt;code&gt;etcd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;HTTP API:  &lt;code&gt;etcd&lt;/code&gt; provides HTTP endpoints that you can make requests to.&lt;/li&gt;
&lt;li&gt;gRPC Endpoints: Alternatively, there is also a gRPC API that you can interact with and its API reference can be found &lt;a href="https://etcd.io/docs/v3.6/dev-guide/api_reference_v3/" rel="noopener noreferrer"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Client Libraries: Multiple SDKs, both official and unofficial exist and you can use these to interact with the API server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a list of client libraries for different languages that you can use in your project.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Maintainer&lt;/th&gt;
&lt;th&gt;API Support&lt;/th&gt;
&lt;th&gt;gRPC&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;clientv3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;etcd-io (official)&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/etcd-io/etcd/tree/main/client/v3" rel="noopener noreferrer"&gt;github.com/etcd-io/etcd/client/v3&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;python-etcd3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/kragniz/python-etcd3" rel="noopener noreferrer"&gt;github.com/kragniz/python-etcd3&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Java&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jetcd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/etcd-io/jetcd" rel="noopener noreferrer"&gt;github.com/etcd-io/jetcd&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JavaScript / Node.js&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;etcd3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/mixer/etcd3" rel="noopener noreferrer"&gt;github.com/mixer/etcd3&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C++&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;etcd-cpp-apiv3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3" rel="noopener noreferrer"&gt;github.com/etcd-cpp-apiv3/etcd-cpp-apiv3&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rust&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rust-etcd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/jimmycuadra/rust-etcd" rel="noopener noreferrer"&gt;github.com/jimmycuadra/rust-etcd&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C# / .NET&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dotnet-etcd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/shubhamranjan/dotnet-etcd" rel="noopener noreferrer"&gt;github.com/shubhamranjan/dotnet-etcd&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Querying etcd
&lt;/h3&gt;

&lt;p&gt;Fetching and storing data in etcd is really simple. Partly because &lt;code&gt;etcd&lt;/code&gt; is just a key value store. Lets go through the ways to query &lt;code&gt;etcd&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Storing data to etcd
&lt;/h4&gt;

&lt;p&gt;Since &lt;code&gt;etcd&lt;/code&gt; is a key value store, data is stored in keys and values, kind of similarity to dictionaries in python or objects in Javascript.&lt;/p&gt;

&lt;p&gt;To store data, you just have to provide a key and the value of data you want to store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl &lt;span class="nt"&gt;--endpoints&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ENDPOINTS&lt;/span&gt; put mykey &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will store &lt;code&gt;"Hello world!"&lt;/code&gt; into the &lt;code&gt;mykey&lt;/code&gt; key. It will create a new key if the provided one does not exist in the database otherwise it will update the existing one.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reading data
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl &lt;span class="nt"&gt;--endpoints&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ENDPOINTS&lt;/span&gt; get mykey
&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; Hello World!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Deleting data
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl &lt;span class="nt"&gt;--endpoints&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ENDPOINTS&lt;/span&gt; del mykey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I mentioned above &lt;code&gt;etcd&lt;/code&gt; has language bindings(SDKs) for different programming languages. They provide utility functions to carry out these actions and manipulate your data. You can find out more operations that can be performed on data in the &lt;a href="https://etcd.io/docs/v3.6/tasks/developer/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, if you are building a golang application, you can embed  the&lt;code&gt;etcd&lt;/code&gt; server into your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  How data is stored in etcd
&lt;/h3&gt;

&lt;p&gt;When working with &lt;code&gt;etcd&lt;/code&gt;, it is better to know how it works and how data is stored internally. This will help you in deciding how to store your data in etcd.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;etcd&lt;/code&gt; uses a flat key-value data model, meaning there are no native hierarchical or nested data structures like JSON trees or SQL tables. Developers tend to use keys that represent some kind of hierarchy e.g. &lt;code&gt;users/staff/jim&lt;/code&gt;. It might look like the data is stored in a hierarchy but it's not. A sample of how it might be stored internally looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"/services/db/user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"/services/db/pass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secret"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These keys look hierarchical, but etcd stores them as flat keys.&lt;/p&gt;

&lt;p&gt;When data is stored, it is encoded in what is called &lt;strong&gt;protocol buffers&lt;/strong&gt; on the disk. This is usually in the data directory which can be specified via the &lt;code&gt;--data-dir&lt;/code&gt; flag when starting an &lt;code&gt;etcd&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;Internally, &lt;code&gt;etcd&lt;/code&gt; uses a storage backend called BoltDB which is a fast embedded key-value store also written in Go, when data is stored in Bolt, it can be indexed using B-tree, which is a self-balancing tree data structure that maintains sorted data and allows fast lookups, insertions, and deletions. Each time data is mutated, it triggers an increment in an internal global revision. This means with etcd, you can read data at different revisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operating etcd
&lt;/h2&gt;

&lt;p&gt;Once you have an &lt;code&gt;etcd&lt;/code&gt; cluster up and running, you will need to manage it to ensure maximum uptime, backups in case of node failures, debugging etc. As an operator you need to be familiar with the different tools and workflows required to manage &lt;code&gt;etcd&lt;/code&gt;. Let's go through the common ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cluster health
&lt;/h3&gt;

&lt;p&gt;When running any system, you need to be able to observe the system health and status. &lt;code&gt;etcdctl&lt;/code&gt; provides commands that can help you view cluster health. Simple run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl &lt;span class="nt"&gt;--endpoints&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ENDPOINTS&lt;/span&gt; endpoint health


&lt;span class="c"&gt;# You can add --write-out=table to have a table like output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl &lt;span class="nt"&gt;--endpoints&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ENDPOINTS&lt;/span&gt; endpoint health


10.240.0.17:2379 is healthy: successfully committed proposal: took &lt;span class="o"&gt;=&lt;/span&gt; 4.384885ms
10.240.0.19:2379 is healthy: successfully committed proposal: took &lt;span class="o"&gt;=&lt;/span&gt; 3.938284ms
10.240.0.18:2379 is healthy: successfully committed proposal: took &lt;span class="o"&gt;=&lt;/span&gt; 3.112832ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;etcdctl --write-out=table --endpoints=$ENDPOINTS endpoint status


+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT      |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 10.240.0.17:2379 | 4917a7ab173fabe7 |  3.5.0  |   45 kB |      true |      false |         4 |      16726 |              16726 |        |
| 10.240.0.18:2379 | 59796ba9cd1bcd72 |  3.5.0  |   45 kB |     false |      false |         4 |      16726 |              16726 |        |
| 10.240.0.19:2379 | 94df724b66343e6c |  3.5.0  |   45 kB |     false |      false |         4 |      16726 |              16726 |        |
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------|
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Snapshots
&lt;/h3&gt;

&lt;p&gt;Snapshots are helpful in case of cluster failure, they enable you to be able to restore your data in case of an issue. Each snapshot can only work on one etcd instance at a time, not the whole cluster. So when creating a snapshot, ensure that you set the &lt;code&gt;--endpoints&lt;/code&gt; flag to the endpoint of only one instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl &lt;span class="nt"&gt;--endpoints&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.240.0.17:2379 snapshot save backup.db


&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; Snapshot saved at backup.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Monitoring etcd
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;etcd&lt;/code&gt; is designed to be easily monitored and it follows industry standards.&lt;/p&gt;

&lt;h4&gt;
  
  
  Metrics
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;etcd&lt;/code&gt; exposes Prometheus compatible metrics that can be pulled in realtime. However these metrics are not persisted across node restarts, so once a node restarts, the metrics counters also restart.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;etcd&lt;/code&gt; exposes its metrics to the &lt;code&gt;/metrics&lt;/code&gt; endpoint and you can spin up a Prometheus server and configure it to pull metrics from that endpoint.&lt;/p&gt;

&lt;h4&gt;
  
  
  Logging
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;etcd&lt;/code&gt; uses standard logging formats, it internally uses zap for logging. THis means all logs contain metadata about the log levels to understand what each line is about. It provides 5 logging levels. Error(for errors that occur), Warning(for temporary but important conditions that may cause errors), Notice(for important information), Info(for normal logs in the program flow) and Debug.&lt;/p&gt;

&lt;p&gt;An etcd log can look something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"warn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2025-07-24T16:58:58.804311+0300"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"caller"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"embed/config.go:1209"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Running http and grpc server on single port. This is not recommended for production."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Hopefully this article has given you a good understanding of &lt;code&gt;etcd&lt;/code&gt; and you can setup, interact and operate a simple etcd cluster. However, you learn by doing so i would suggest you set up your own cluster and play around with it. Etcd has a wide range of use case scenarios and to even gain the full power of this system, I would recommend trying to use a programming client, preferably the official Go client to interact and use &lt;code&gt;etcd&lt;/code&gt;. That being said, that's all I had for today. Thanks for reading.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>database</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Designing The Conveyor CI Pipeline Engine</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Tue, 22 Jul 2025 10:02:54 +0000</pubDate>
      <link>https://forem.com/jimjunior/designing-the-conveyor-ci-pipeline-engine-22f1</link>
      <guid>https://forem.com/jimjunior/designing-the-conveyor-ci-pipeline-engine-22f1</guid>
      <description>&lt;p&gt;In CI/CD Systems, there is a concept of pipelines. Pipelines define the steps of how code changes throughout the entire CI/CD process. Multiple systems like Github Actions, GitLab CI/CD, Jenkins etc all have this functionality of pipelines natively embedded within them, something that &lt;a href="https://conveyor.open.ug/" rel="noopener noreferrer"&gt;Conveyor CI&lt;/a&gt; currently lacks. You could engineer a walkaround to implement your own pipeline-like functionality in Conveyor but natively, this functionality does not exist and this is a serious downside existing within Conveyor.&lt;/p&gt;

&lt;p&gt;This got me thinking, how could I implement it. As you might guess from the name, Conveyor CI was inspired by how a &lt;a href="https://en.wikipedia.org/wiki/Conveyor_system" rel="noopener noreferrer"&gt;conveyor system&lt;/a&gt; in an industry works. Take an example of a car manufacturing factory, the skeleton of the car moves along the conveyor system and at each step there is a dedicated robot in charge of attaching a certain component to the car to carry out a certain function. By the time the skeleton reached the final stage, its a complete car. From the start I wanted to adopt this kind of concept as the core paradigm of Conveyor CI whereby a resource(e.g. source code) moves on a belt and at the end of the belt, all necessary actions are carried out.&lt;/p&gt;

&lt;p&gt;With this in mind, I split the Conveyor CI into components in order to easily understand it adopting from components of a conveyor system ie. Package, conveyor belt, and Peripheral equipment. In that I came up with the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resource&lt;/strong&gt;: The resource is an internal object in Conveyor that represents what is being acted upon throughout the CI/CD process. Think of it as the Package on a conveyor system. It can represent anything from source code, and application, a program etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drivers&lt;/strong&gt;: Drivers are software components that carry out certain actions depending on the state defined in the Resource. Think of them as the peripheral devices that act on a package as it moves along a conveyor system. In this case the Packages are the Resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice I have not mentioned a component that corresponds to the conveyor belt. That is because currently there was none, and to create one was where pipelines come in.&lt;/p&gt;

&lt;p&gt;So currently in Conveyor CI we have Resources and Drivers. They are generally mature enough as individual components and although you could create an entire CI/CD tool with these only, there are still some issues. Mainly is that there is no order of execution of drivers upon a Resource. This means that once a Resource event occurs, all drivers execute their corresponding actions whenever they receive the event and they do this in no orderly fashion. This means it is not possible to predict what action will occur at what point in the CI/CD process and you also can’t create dependency actions that depend on others being pre-executed. An example is if you have a workflow whereby you compile your source code then upload the output program to a distribution server, You might have two drivers, one for compiling and another for uploading. In the current implementation, these drivers will execute at once, yet the uploading driver should depend on the constraint that the compiling driver is done executing.&lt;/p&gt;

&lt;p&gt;To fix this we have to come up with a way to define a workflow that drivers must follow when carrying out their executions, something that defines the steps and order that these drivers will follow throughout execution. Something to act as the conveyor system. This is where Pipelines Come in. They will define the order of execution followed by drivers. The pipeline can be an object containing the configuration defining the order followed by drivers.&lt;/p&gt;

&lt;p&gt;Pipelines also introduce more possibilities like shared context among driver executions, meaning, a pipeline can define some metadata that is shared and used across all the drivers that are executing upon a resource in that pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Pipelines
&lt;/h2&gt;

&lt;p&gt;Now that I had come up with a high level design of what pipelines are expected to work, I had to move on to designing an implementation that would easily fit in into the existing Conveyor CI implementation. Inorder to create a implementation that is good, i set a few constraints that i had to follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pipelines should not introduce breaking changes that might interfere with the Developer experience and development paradigm for developers developing drivers. It should be more of an incremental change, building upon the already existing development paradigm without breaking already existing codebases.&lt;/li&gt;
&lt;li&gt;Minimize the risk of over engineering the system while trying to follow constraint one. This is because usually one trade off of maintaining a good developer experience is that you might end up over engineering the system.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With these design constraints in mind, I embarked on designing the Pipeline engine. First I started with the usage workflow from the user viewpoint i.e. the process that will be followed by a developer when creating a pipeline and using it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use case Workflows
&lt;/h3&gt;

&lt;p&gt;Considering that developer experience is one of the most important concepts for the success of a tool, I had to come up with a really easy to understand workflow and also not break the already existing usage workflows in Conveyor CI. I came up with this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First a pipeline is designed by the developer choosing their appropriate drivers and resources and arranging them in a desired order.&lt;/li&gt;
&lt;li&gt;The pipeline is then sent to the Conveyor CI API Server to be registered and saved.&lt;/li&gt;
&lt;li&gt;Once its saved then a Resource that is using that pipeline is created and sent to the API server&lt;/li&gt;
&lt;li&gt;Then the Pipeline Engine appropriately routes the resource to the drivers.&lt;/li&gt;
&lt;li&gt;Driver do there work&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How the Engine Works
&lt;/h3&gt;

&lt;p&gt;With this in mind, I came up with a system design that can accomplish these functions but also act as an incrementation on the existing Conveyor base.&lt;/p&gt;

&lt;p&gt;First a new state object called a &lt;em&gt;Pipeline&lt;/em&gt; was introduced into the system. This object would store and represent information about a pipeline throughout the stages of execution. It would store the order of execution followed by drivers and additional context information/metadata that is required by the driver in said pipeline.&lt;/p&gt;

&lt;p&gt;I also had to introduce a &lt;em&gt;Driver Result&lt;/em&gt; event object. This object, acting as an event, was to be used by drivers to communicate the result of the &lt;code&gt;Reconcile&lt;/code&gt; function(The function that is run when a driver receives a resource to act upon). This would be used to inform the Pipeline Engine if a desired driver operation was successful or not.&lt;/p&gt;

&lt;p&gt;In order to maintain realtime seamless execution, a new execution process running concurrently with the API Server had to be introduced to Conveyor CI's core runtime program. This would be in charge of listening for new resources attached to pipelines, routing those resources to drivers in a pre-defined order as defined in the &lt;em&gt;Pipeline&lt;/em&gt; objects, and watching for pipeline realworld state changes and reconciling it to the state stored in the Database. This process is what I named the &lt;strong&gt;Pipeline Engine&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Execution Workflow
&lt;/h4&gt;

&lt;p&gt;Having introduced the required components, I came up with this workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user sends a POST request to the Conveyor API server to register their Pipeline, this is then saved to the ETCD Database.&lt;/li&gt;
&lt;li&gt;Then the user creates a pipeline Resource(a resource that depends on that pipeline), also via the API Server.&lt;/li&gt;
&lt;li&gt;When the API server receives and saves this Resource to the DB, it sends an Event to the Pipeline Engine.&lt;/li&gt;
&lt;li&gt;The Pipeline Engine receives the Event and using the event metadata it collects the Pipeline information from the DB and depending on the driver execution order defined by the pipeline it begins to send events to the drivers.&lt;/li&gt;
&lt;li&gt;Once the Driver has finished to carry out its functions, it returns a &lt;em&gt;Driver Result&lt;/em&gt; and the Driver Manager sends that result as an event back to the Pipeline Engine.&lt;/li&gt;
&lt;li&gt;When the Result event is received, the Pipeline engine can then move on to sending the event to the next driver. If no result event has been received yet from the Driver, the Engine wont move on to the next Driver.&lt;/li&gt;
&lt;li&gt;Lastly, if the result event on one driver indicates that the Driver execution failed or an error occurred. The pipeline engine won't send events to the remaining drivers and will rather register the pipeline as stopped or done with a status of failed. Else it will finish execution of all drivers and still register the pipeline as done/complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  A Simple Diagram representing this process
&lt;/h5&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%2Fy738xdicm6f8uzq2flal.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%2Fy738xdicm6f8uzq2flal.png" alt="Pipeline Ecexution flow" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Technical Details
&lt;/h4&gt;

&lt;p&gt;I have managed to describe the high level working of how the Pipeline engine would work. But beyond that, integrating such functionality into the Conveyor Stack requires clarification on how to implement the different components technically.&lt;/p&gt;

&lt;p&gt;Starting off with the Pipeline engine itself, we said it's a process running concurrently with the API Server in the same program. Considering that the Conveyor CI core software program is written in the &lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;Go programming language&lt;/a&gt; and Go as a language has inbuilt concurrency via the &lt;a href="https://www.geeksforgeeks.org/go-language/goroutines-concurrency-in-golang/" rel="noopener noreferrer"&gt;Go routines&lt;/a&gt;, this means that the Pipeline Engine would better be a go routine running alongside the API server on the same process. One upside of this mechanism of embedding the Pipeline engine into the same process with the API Server is that we will require little updates to the metrics and monitoring codebase in order to add metrics for monitoring the Pipeline Engine.&lt;/p&gt;

&lt;p&gt;Inorder to achieve the realtime nature of execution among these separate execution runtimes, we utilize events via &lt;a href="https://docs.nats.io/nats-concepts/jetstream" rel="noopener noreferrer"&gt;NATS Jetstream&lt;/a&gt;, the message broker that is already being used by Conveyor. This means that the Pipeline Engine performs both the roles of an Event publisher and subscriber. When new Pipeline resources are created, an event is sent to the Pipeline engine via JetStream. In this scenario, the Pipeline Engine is running as a &lt;a href="https://docs.nats.io/nats-concepts/jetstream/consumers" rel="noopener noreferrer"&gt;Jetstream Consumer&lt;/a&gt;, listening for events via the &lt;code&gt;pipeline&lt;/code&gt; &lt;a href="https://docs.nats.io/nats-concepts/jetstream/streams" rel="noopener noreferrer"&gt;stream&lt;/a&gt;. Upon receiving an event, it will collect the pipeline data from ETCD and collect all the driver names. Using the driver names it will now become an event publisher and publish to the &lt;code&gt;messages&lt;/code&gt; stream(the default stream in which drivers listen for events) and use the subjects with the following semantics &lt;code&gt;{DRIVER_NAME}.resources.{RESOURCE_NAME}&lt;/code&gt; with the placeholders &lt;code&gt;{DRIVER_NAME}&lt;/code&gt; and &lt;code&gt;{RESOURCE_NAME}&lt;/code&gt; referring to the name of the driver to publish to and the name of the resource being published respectively. All we have to do now is update the driver managers of the drivers, to include the new subject in the filtered subjects field of there Jetstream consumers. This will require updating all the SDKs though. With this, we have a complete working Pipeline engine.&lt;/p&gt;

&lt;p&gt;Finally to keep track of Driver executions that belong to one pipeline, we utilize an already existing concept in Conveyor CI which is the &lt;code&gt;Run ID&lt;/code&gt;. This is a &lt;em&gt;UUID&lt;/em&gt; string that identifies individual driver runs/executions upon a resource. In a pipeline, we maintain the same run ID throughout executions in a single pipeline execution.&lt;/p&gt;

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

&lt;p&gt;The whole point of Pipelines was to introduce ordered execution of drivers on a resource but this feature can act as a fundamental building block for more robust and important functionality to be integrated into Conveyor CI. With this I have to acknowledge that Conveyor CI is still a really immature project, it really still has a long way to go and still lacks important features e.g. resource versioning etc. But one step at a time. Just like Rome wasn't built in a day, You can build a Cloud native software framework in a short period of time.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>opensource</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>A Guide to contributing to the Conveyor CI Driver Runtime</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Fri, 20 Jun 2025 20:38:10 +0000</pubDate>
      <link>https://forem.com/jimjunior/a-guide-to-contributing-to-the-conveyor-ci-driver-runtime-e06</link>
      <guid>https://forem.com/jimjunior/a-guide-to-contributing-to-the-conveyor-ci-driver-runtime-e06</guid>
      <description>&lt;p&gt;A detailed guide to contributing to the Conveyor CI Driver runtime.&lt;/p&gt;

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

&lt;p&gt;A brief introduction on the Conveyor CI Driver runtime.&lt;/p&gt;

&lt;p&gt;The Conveyor CI Driver runtime is a collection of SDKs(Software Development Kits) used by developers to build Conveyor CI &lt;a href="https://conveyor.open.ug/docs/concepts/drivers" rel="noopener noreferrer"&gt;Drivers&lt;/a&gt;. These SDKs are libraries that contain Utility functions that expose different Conveyor CI functionality. In this Guide we shall explore how one can contribute to the Conveyor CI Driver Runtime. This Guide is written to be language agnostic, meaning it can guide you to build or contribute to a driver runtime of your language of choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Driver Runtime Works
&lt;/h2&gt;

&lt;p&gt;Lets explore what the Runtime is&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the Driver Runtime
&lt;/h3&gt;

&lt;p&gt;You can think of the Driver runtime as the wrapper of Driver applications and is the environment in which Drivers execute. You can define it as the software layer that provides necessary services, infrastructure, and behavior for executing the driver. Think of how language runtime work like the JVM, Go Runtime, V8, CPython etc. If you where to write a program in those languages, run in an environment that contains your code and the language runtime.&lt;/p&gt;

&lt;p&gt;So even when building a Conveyor CI Driver, the final program is a combination of the Driver runtime and your custom Driver code.&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%2F1d0gh4zv1r6cbpp45rwi.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%2F1d0gh4zv1r6cbpp45rwi.png" alt="Conveyor CI" width="431" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What Features does it Provide
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, the driver runtime provides necessary services and utilities for building CI Drivers. Some of these features are not just exclusive to the runtime but rather and extension of the base features provided by the Conveyor CI Ecosystem. These features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A mealtime Event System: The driver runtime is designed to ensure drivers receive and publish events within the Conveyor System to ensure quick and instant execution of tasks.&lt;/li&gt;
&lt;li&gt;Log Management: The Driver runtime provided a &lt;a href="https://conveyor.open.ug/docs/concepts/drivers#driver-logger" rel="noopener noreferrer"&gt;Driver Logger&lt;/a&gt;, that provides drivers with functionality to export and save necessary logs in real-time. These can then be fetched or streamed in real-time or for future use.&lt;/li&gt;
&lt;li&gt;Horizontal Scaling: The Driver runtime provides out of the box Horizontal Scaling to Drivers, meaning once you build a driver. It comes with the ability to horizontally scale into a distributed system efficiently without you writing any custom code. This feature can be really important in Cloud Native environments.&lt;/li&gt;
&lt;li&gt;Conveyor CI API Server Interaction: No driver runtime package is complete without utility functions to interact with the Conveyor API Server to for example create and manipulate Resources.&lt;/li&gt;
&lt;li&gt;Observability out of the Box: The Driver runtime is designed to provide a comprehensive observability platform that follows all the three pillars that include Metrics, Tracing and Logging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And many more...&lt;/p&gt;

&lt;h3&gt;
  
  
  Driver runtime Components
&lt;/h3&gt;

&lt;p&gt;The Driver runtime contains mainly two components. The Driver Manager and the Client Library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Driver Manager
&lt;/h3&gt;

&lt;p&gt;The Driver manager, the component that encapsulates the Driver and provides utilities that are used by the Driver. By design, it is usually a &lt;code&gt;Class&lt;/code&gt; or a Class-like data type that has a &lt;code&gt;run()&lt;/code&gt; method that is called to start the program. Its is responsible for the following functionality.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listening for events from Conveyor CI sent via NATS Jetstream: This is accomplished using JetStream &lt;a href="https://docs.nats.io/nats-concepts/jetstream/streams" rel="noopener noreferrer"&gt;Streams&lt;/a&gt; and &lt;a href="https://docs.nats.io/nats-concepts/jetstream/consumers" rel="noopener noreferrer"&gt;Consumers&lt;/a&gt;. The Driver manager contains a Consumer that listens for events from the &lt;code&gt;messages&lt;/code&gt; stream and filters out subjects depending on the resources that the Driver defines to listen to.&lt;/li&gt;
&lt;li&gt;Provides the Driver Logger to the Driver. The driver logger is a component that collects logs from the driver and send them to Grafana Loki for Storage. After storing them it also streams them via a NATS connection on the subject with the following semantics &lt;code&gt;driver:{DRIVER_NAME}:logs:{RUN_ID}&lt;/code&gt; with the place holders &lt;code&gt;DRIVER_NAME&lt;/code&gt; and &lt;code&gt;RUN_ID&lt;/code&gt; referring to the name of the driver and the current run id respectively.&lt;/li&gt;
&lt;li&gt;Runs the &lt;code&gt;Reconcile&lt;/code&gt; function of the Drivers upon each event from JetStream and passes in the appropriate parameters.&lt;/li&gt;
&lt;/ul&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%2F421bcovdyecrwqih52k9.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%2F421bcovdyecrwqih52k9.png" alt="Conveyor CI Driver Manager Class Diagram" width="221" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Driver Logger
&lt;/h3&gt;

&lt;p&gt;The Driver logger component is in charge of collecting logs from the Driver, sending them to Loki Grafana for storage and also streaming them via the NATs connection. It accomplished this by following the rules stated above in the Driver manager section&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%2Fk6sgo8vyt4nx2xq5g7sj.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%2Fk6sgo8vyt4nx2xq5g7sj.png" alt="Conveyor CI Driver Logger Class Diagram" width="301" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Driver
&lt;/h3&gt;

&lt;p&gt;The Driver is the main component that developers interact with. It contains the &lt;code&gt;Reconcile&lt;/code&gt; function that contains the custom code that includes the drivers intended functionality. When initializing it, the constructor must require the developer to specify the Resources that the driver wants to control, the driver name and also the Reconcile function. The Reconcile function, is a function that takes in these parameters. &lt;code&gt;payload&lt;/code&gt;, &lt;code&gt;event&lt;/code&gt;, &lt;code&gt;driverName&lt;/code&gt;, &lt;code&gt;logger&lt;/code&gt;.&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%2F9xfwrqb5p91o4fzjo8q8.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%2F9xfwrqb5p91o4fzjo8q8.png" alt="Conveyor CI Driver Class Diagram" width="351" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Client Library
&lt;/h3&gt;

&lt;p&gt;The client Library is a component of the driver runtime that contains functions that interact with the Conveyor CI API Server. these are normal HTTP Requests to the API Server. It also has the ability to fetch Conveyor CI API Server metadata like the API Host and Port.&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%2Fi17wwzu309y25jcvg1rx.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%2Fi17wwzu309y25jcvg1rx.png" alt="Conveyor CI API Client Diagram" width="481" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The Class Diagrams above represent the general expected Structure of those components however due to differences is language syntax and semantics in programming languages. The structure can be tweaked to provide the Developers a good user experience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Component Diagram
&lt;/h3&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%2Fss3bqx65njtwmsmuhnei.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%2Fss3bqx65njtwmsmuhnei.png" alt="Conveyor CI Driver Runtime Component Diagram" width="581" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing Guide
&lt;/h2&gt;

&lt;p&gt;Now that you have a solid understanding of the high level structure of the Driver runtime. Lets move on to the practical guide of the Workflow of Contributing to or building a driver runtime SDK package. Shall first go through the Development Environment setup then also go through the general contribution workflow followed when contributing to the source code of official Conveyor CI Driver runtime SDKs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment Setup
&lt;/h3&gt;

&lt;p&gt;To setup your environment to run conveyor, all you need is to have Docker installed on you computer. You can head over to the &lt;a href="https://docs.docker.com/engine/install/" rel="noopener noreferrer"&gt;Docker Installation Page&lt;/a&gt; to install it on your system.&lt;/p&gt;

&lt;p&gt;Once you have it, you will need to download two configuration files that are used to run Conveyor API Server and System dependencies. Head over to the &lt;a href="https://github.com/open-ug/conveyor/releases" rel="noopener noreferrer"&gt;Conveyor CI Releases Page&lt;/a&gt; and on the assests of the latest release, download &lt;code&gt;compose.yml&lt;/code&gt; and &lt;code&gt;loki.yml&lt;/code&gt; and store them in the same directory. Or you can simply run these commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.github.com/repos/open-ug/conveyor/releases/latest | &lt;span class="nb"&gt;grep &lt;/span&gt;browser_download_url | &lt;span class="nb"&gt;grep &lt;/span&gt;compose.yml | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 4 | xargs curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; compose.yml

curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.github.com/repos/open-ug/conveyor/releases/latest | &lt;span class="nb"&gt;grep &lt;/span&gt;browser_download_url | &lt;span class="nb"&gt;grep &lt;/span&gt;loki.yml | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 4 | xargs curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; loki.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have both those files in the same directory, you can then start the Conveyor CI Containers by running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up

&lt;span class="c"&gt;# OR&lt;/span&gt;

docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Take note of the ports and host that these containers run no, or you can just open the &lt;code&gt;compose.yml&lt;/code&gt; file and see what ports are being exposed so when you are developing the packages, you are aware what ports and host to connect to. Also note that in-case its a requirement to set different Ports or you want to configure your own ports. always follow the convention of setting environment variable &lt;code&gt;CONVEYOR_SERVER_HOST&lt;/code&gt; and &lt;code&gt;CONVEYOR_SERVER_PORT&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you have everything running your are good to go onto development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow
&lt;/h3&gt;

&lt;p&gt;We welcome contributions of all kinds, Whether you're fixing a typo, reporting a bug, improving documentation, or adding a new feature, your help is appreciated. However when contributing to the Driver runtime, you need to follow a specific contribution workflow.&lt;/p&gt;

&lt;p&gt;There are multiple kinds of contributions we follow and they include&lt;/p&gt;

&lt;h4&gt;
  
  
  Reporting Bugs
&lt;/h4&gt;

&lt;p&gt;If you find a bug, you can help fix it by submiting and issue to the appropriate repository. within your issue Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A clear and descriptive title&lt;/li&gt;
&lt;li&gt;Steps to reproduce the issue&lt;/li&gt;
&lt;li&gt;What you expected to happen&lt;/li&gt;
&lt;li&gt;What actually happened&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the repository has an issue tempate. you should follow it.&lt;/p&gt;

&lt;p&gt;Before submitting, check if the issue already exists in the repository issue list.&lt;/p&gt;

&lt;h4&gt;
  
  
  Suggesting Enhancements
&lt;/h4&gt;

&lt;p&gt;We also welcome feature suggestions and ideas for improvement. When submitting a feature request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explain &lt;strong&gt;why&lt;/strong&gt; the feature is useful&lt;/li&gt;
&lt;li&gt;Provide &lt;strong&gt;example scenarios&lt;/strong&gt; where it would help&lt;/li&gt;
&lt;li&gt;Suggest a &lt;strong&gt;possible implementation&lt;/strong&gt;, if you have one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try to keep requests focused and concise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submitting Code Changes
&lt;/h3&gt;

&lt;p&gt;Once you have identified and issue or an enhancement you would like to work on. you can folloe this workflow to submit your code changes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fork&lt;/strong&gt; the repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clone&lt;/strong&gt; your fork:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git clone https://github.com/your-username/project-name.git
   &lt;span class="nb"&gt;cd &lt;/span&gt;project-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a new branch&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature/your-feature-name 

   &lt;span class="c"&gt;# OR &lt;/span&gt;

   git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; fix/your-fixture-name 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Make your changes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write or update tests&lt;/strong&gt;, if applicable&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commit&lt;/strong&gt; your changes with clear messages:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: add new feature X"&lt;/span&gt;
&lt;span class="c"&gt;# OR&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix: solved issue Y"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Push&lt;/strong&gt; your changes:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin feature/your-feature-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open a Pull Request&lt;/strong&gt; on the GitHub repository and describe what you’ve done&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pull requests should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focused on a single change&lt;/li&gt;
&lt;li&gt;Thoroughly tested&lt;/li&gt;
&lt;li&gt;Aligned with project’s code style and guidelines&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Improving Tests or Documentation
&lt;/h3&gt;

&lt;p&gt;Improving test coverage and documentation is highly valuable. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add test cases for untested components&lt;/li&gt;
&lt;li&gt;Update outdated documentation&lt;/li&gt;
&lt;li&gt;Fix typos or formatting issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No contribution is too small!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;A few resources that you might find useful when trying to understand the Conveyor System&lt;/p&gt;

&lt;p&gt;NATS AND JETSTREAM RESOURCES&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.nats.io/" rel="noopener noreferrer"&gt;NATS Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/334XuMma1fk?si=-0K7lhFuKDwgj5jj" rel="noopener noreferrer"&gt;JetStream Consumers Youtube Video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nats-io/nats.py" rel="noopener noreferrer"&gt;NATS Python Client Library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CONVEYOR GO PACKAGE AND DRIVER RUNTIME&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/open-ug/conveyor/tree/main/pkg" rel="noopener noreferrer"&gt;Repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OTHER RESOURCES&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chatgpt.com/share/68549cda-2640-8005-ac59-03a1a6a348c9" rel="noopener noreferrer"&gt;Sending Logs to Loki Grafana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.confluent.io/learn/event-driven-architecture/" rel="noopener noreferrer"&gt;Event Driven Architecture Explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://conveyor.open.ug" rel="noopener noreferrer"&gt;Conveyor CI Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>programming</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Designing the Conveyor CI Scheduling Mechanism</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Mon, 16 Jun 2025 17:08:55 +0000</pubDate>
      <link>https://forem.com/jimjunior/designing-the-conveyor-ci-scheduling-mechanism-4f6j</link>
      <guid>https://forem.com/jimjunior/designing-the-conveyor-ci-scheduling-mechanism-4f6j</guid>
      <description>&lt;p&gt;A brief introduction on &lt;a href="https://conveyor.open.ug/" rel="noopener noreferrer"&gt;Conveyor CI&lt;/a&gt;. Its is a Software framework fdesigned to simplify the development of scalable and reliable CI/CD platforms by eliminating common pain points that a team ight to run into inorder to build a Scalable, reliable CI/CD Platform. It provides a simplified  streamlined, developer-friendly development paradigm for building robust custon CI/CD Tools.&lt;/p&gt;

&lt;p&gt;Recently a team that uses Conveyor CI as a dependency had an inquiry on how a CI Driver in conveyor can handle multiple instances of running CI processes without overloading the host node. This is a really important consideration I had not thought of, and for a system that runs multiple tasks in concurrency, being conscious of system resources is a really important consideration.&lt;/p&gt;

&lt;p&gt;This got me thinking, how best can one approach such an issue. I thought of multiple solutions to this, I had considered introducing a consensus algorithm like raft, however this wasn't really an effective solution. Looking into how the Kubernetes Kube Scheduler efficiently allocates Pods to run on different nodes taking into considerations or the resource constraints of both the Pod to be allocated and the nodes its to be allocated to gave me an inspiration of having to design a custom &lt;a href="https://en.wikipedia.org/wiki/Scheduling_(computing)" rel="noopener noreferrer"&gt;scheduling algorithm&lt;/a&gt; that had to to take into consideration of the multiple, use case scenarios of CI/CD tools and what constraints they operate under.&lt;/p&gt;

&lt;p&gt;I had to first come up with the high level requirements and problem statements for the CI Drivers in order to tackle this issue.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;: In large production grade CI/CD Pipelines, multiple requests can initiated at the same time and due to the underlying concurrent and instant execution of CI tasks by Conveyor CI Drivers, this introduces an issue of possible node crashed or driver process termination by the Operating system due to the host node running out of system resources. Also currently, drivers don't support horizontal scalability, something that is really important for CI/CD tools that are expected to run multiple workloads at a certain point in time considering vertical scalability tends to be finite and also costs tend to increase exponentially the more you scale vertically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From that problem statement I managed to identify two high level requirements.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drivers should support horizontal scaling&lt;/li&gt;
&lt;li&gt;The Driver runtime should take into consideration available node resources before executed a task&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After a long night of insightful research, I came up with a bunch of solutions for this, these would assist me in designing the scheduling algorithm. I would call the &lt;strong&gt;functional or technical requirements&lt;/strong&gt; if that's the right term to use.&lt;/p&gt;

&lt;p&gt;The algorithm was to take inspiration and be a combination of multiple OS scheduling algorithms, with the help of a consensus algorithm to handle and support the distributed properties of the Driver instances.&lt;/p&gt;

&lt;p&gt;To solve the issue of the possibility of overloading the server with tasks that require system resources than available, First i had to remove the instant execution of tasks and introduce a task queue, this would enable me to be able to incorporate a &lt;a href="https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)" rel="noopener noreferrer"&gt;FIFO-like&lt;/a&gt; architecture as the base algorithm for scheduling tasks in a normal flow. Taking into consideration that in certain organisations, CI/CD tools are designed with the concept of having some tasks prioritized due to multiple factors, say if they would like to prioritize tasks from paying users. I also decided to incorporate a &lt;a href="https://en.wikipedia.org/wiki/Dynamic_priority_scheduling" rel="noopener noreferrer"&gt;Priority Scheduling Algorithm&lt;/a&gt; as the second level algorithm. Lastly, I had to introduce a &lt;a href="https://www.sciencedirect.com/science/article/pii/S1474667017377388" rel="noopener noreferrer"&gt;constraint based scheduling&lt;/a&gt; algorithm as the last determining algorithm, these constraints can be the available system resources etc. To support such a complex algorithm, I had to introduce &lt;a href="https://en.wikipedia.org/wiki/Heuristic" rel="noopener noreferrer"&gt;heuristics&lt;/a&gt; that are computed by the Driver from a resource specification or defined directly in the resource specification. These heuristics can be task priority, available node resources etc.&lt;/p&gt;

&lt;p&gt;On the issue of horizontal scalability, I decided to incorprate NATS Jetsream Consumers. This would enable distributed processing of Driver tasks laveraging the already existing powerfull features that NATS Jetstream offers out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Mechanism works
&lt;/h2&gt;

&lt;p&gt;Alright, let's get into how it works. At the core, the tasks are organized in a priority queue, where tasks with a high priority are executed first and in instances where the priority number is equal, they are executed based on a FIFO order based on a timestamp.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preventing resource contention
&lt;/h3&gt;

&lt;p&gt;Within the tasks Resource specification, I introduced an optional field called &lt;code&gt;resourceUsage&lt;/code&gt; that defines the expected resource usage for the task during execution. This acts as heuristic that helps the driver runtime decide whether a certain driver instance is allowed to run the task. It compares the available host node system resources and evaluates whether running that task will not cause resource contention or Out of Memory Errors. This constraint based scheduling considers any node as long as adding the task won't exit 100 percent usage of the resources, that's to say, even if a node is using up to 90% of its resources and adding the task will push it to 99% it will still schedule it on that node. it also tends to prioritize driver instances running on nodes with lower resource usage than the others, say if node A is using 60% and node B is using 55%, it's more likely to consider node B than A. But this will not always be the case, sometimes other factors might cause us to consider node A. Such a mechanism will ensure that the possibility of node failures due to resource contention is really low.&lt;/p&gt;

&lt;p&gt;Here is some Pseudo code example to demonstrate the algorithm structure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Initialize&lt;/span&gt; &lt;span class="n"&gt;priorityQueue&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="nf"&gt;queue &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;higher&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FIFO&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Initialize&lt;/span&gt; &lt;span class="n"&gt;nodeList&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;

&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="nf"&gt;scheduleTasks&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;priorityQueue&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;priorityQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;eligibleNodes&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodeList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canAccommodate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resourceUsage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;eligibleNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;eligibleNodes&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;priorityQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Retry later
&lt;/span&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;Sort&lt;/span&gt; &lt;span class="n"&gt;eligibleNodes&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;resourceUsage &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;selectedNode&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nf"&gt;selectBestNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eligibleNodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Assign task to selectedNode
&lt;/span&gt;        &lt;span class="n"&gt;Update&lt;/span&gt; &lt;span class="n"&gt;selectedNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resourceUsage&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resourceUsage&lt;/span&gt;

&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canAccommodate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resourceUsage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resourceUsage&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;resourceUsage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;≤&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;

&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="nf"&gt;selectBestNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Prefer lower resource usage, but allow for flexibility
&lt;/span&gt;    &lt;span class="c1"&gt;# Example heuristic: select node with minimal usage
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# or apply more complex scoring if needed
&lt;/span&gt;
&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="nf"&gt;addTaskToQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;priorityQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# uses (priority, timestamp) as sort key
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Horizontal Scaling
&lt;/h3&gt;

&lt;p&gt;When it comes to horizontal scaling, I decided to re-engineer the Drivers to run as a distributed system in cases where there are multiple driver instances running. This distributed system is powered by NATS JetStream Consumers&lt;/p&gt;

&lt;p&gt;At the core each Conveyor CI Driver operates as a &lt;a href="https://docs.nats.io/nats-concepts/jetstream/consumers" rel="noopener noreferrer"&gt;JetStream Consumer&lt;/a&gt;. They listen to a &lt;a href="https://docs.nats.io/nats-concepts/jetstream/streams" rel="noopener noreferrer"&gt;stream&lt;/a&gt; which filters out required messages and pushes them to the driver. The driver then carries out the required tasks depending on the payload recieved from the stream.&lt;/p&gt;

&lt;p&gt;The Drivers are configured to use Explicit acknowledgement with the value set to 1 so that each task it handles one by one and this would prevent a possibility of one driver handling multiple tasks at once possibly causing resource contention. Acknowledgement occurs once the message is recieved by the driver. We also set the Delivery policy to All, so that no task is skipped and all tasks are handled. Another feature that Jet&lt;/p&gt;

&lt;p&gt;By utilising JetStream, it enables driver to be able to infinitly scale horizontally and become more failt torolent by utilizing various inbuilt NATS features.&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%2Fay580y70jy2hrx66u45p.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%2Fay580y70jy2hrx66u45p.png" width="667" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Designing a robust scheduling algorithm for Conveyor CI Drivers was a necessary and will definetely improve the functionality and reliability of the Drivers when the scheduling mechanism is added to the Conveyor CI codebase. By combining FIFO, priority-based, and constraint-aware scheduling with a distributed model powered by JetStream, I’ve managed to create a system that not only prevents resource overloads but also opens the door for true horizontal scaling and more features like fault tolorence. This isn’t just a theoretical improvement—it directly addresses real-world pain points for teams running concurrent workloads on shared infrastructure. There’s still room to refine and optimize, but this foundation sets the stage for building CI/CD systems that are both resilient and adaptive under pressure. This might be the greatest improvement to Conveyor CI yet.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>softwaredevelopment</category>
      <category>cicd</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How to deploy an anonymous website on the Dark Web</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Wed, 19 Mar 2025 19:02:16 +0000</pubDate>
      <link>https://forem.com/jimjunior/how-to-deploy-an-anonymous-website-on-the-dark-web-298j</link>
      <guid>https://forem.com/jimjunior/how-to-deploy-an-anonymous-website-on-the-dark-web-298j</guid>
      <description>&lt;p&gt;Have you ever wondered what the Dark Web is? How to access it? Or even better, how to deploy your site to the dark web? Well I got you today. In this article we shall look at how you can deploy your site on the Tor network, which is a Dark web that allows clients and Servers to navigate and exist on the Internet Anonymously.&lt;/p&gt;

&lt;p&gt;The Internet is not all you see, it's divided into 2 parts, The Clearnet and The Darknet. They are differentiated by Traceability. You see on the Clearnet, when a message is sent over the network, it's easy to trace the client that sent it and the destination server that it was sent to. That's not the case on the Darknet, on the Darknet the location/IP address of the server and the client are not traceable. The client sends a message to the server but doesn't know where it's located.&lt;/p&gt;

&lt;p&gt;Take an example of &lt;a href="https://www.youtube.com" rel="noopener noreferrer"&gt;youtube.com&lt;/a&gt;, If we do an &lt;a href="https://www.nslookup.io/domains/youtube.com/webservers/" rel="noopener noreferrer"&gt;NSLookup&lt;/a&gt;, we can identify the IP Address, Location and ISP of the server, in this case as of now its, 142.251.12.190 with location in Singapore and Google LLS ISP.&lt;/p&gt;

&lt;p&gt;This traceability is due to the underlying design of the TCP/IP Protocol where communication, whether using TCP or UDP for communication, the IP address of the server and client is traceable and known.&lt;/p&gt;

&lt;p&gt;Even in cases where we try to hide our identity over the internet, there is still some form of traceability. In large organisations, they usually try to make the client address private and all traffic is routed through a NAT. This keeps the address of the client hidden but the IP Address of the NAT is public to the Internet. Another way we attempt to navigate the internet anonymously is using VPN Systems, these work by routing the traffic from the client through a relay system or server and then this routes the traffic to the destination server. However much the IP Address of the client does not make it to the mainstream Internet, that of the VPN System is public to the Internet. Generally, there is always some form of traceability on the Clearnet&lt;/p&gt;

&lt;p&gt;On the Darknet this traceability is not there, Darknets are designed with confidentiality, privacy, and freedom in mind, so maximum anonymity is ensured. Notice i just said "Darknets", this is because there are more than one form of Darknet. Actually the term "Darknet" refers to any overlay network that requires special software configurations and authorisation to access and is not indexed by search engines. Some forms of darknet technologies include I2P (Invisible Internet Project), Freenet, ZeroNet etc. But the most popular so far is Tor (The Onion Router) and its the one we shall be discussing and using in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Onion Router (Tor)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.torproject.org/" rel="noopener noreferrer"&gt;The Onion Router&lt;/a&gt;, often known as Tor, is one of the forms of darknet technologies that is under operation and so far the most popular. It runs on a core technology known as Onion Routing that was developed in the mid 1990's by the United States Naval Research Laboratory to protect American intelligence communications online. In 2004, the Naval Research Laboratory released the code for Tor under a free license and now runs on free and open-source software and more than seven thousand volunteer-operated relays worldwide.&lt;/p&gt;

&lt;p&gt;It is built with the principles of confidentiality, privacy, and freedom in mind. It enables anonymous communication over the internet between clients and servers. To accomplish this, strong encryption algorithms/technologies like SSL/TLS and relay servers are utilised to prevent message &lt;a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack" rel="noopener noreferrer"&gt;Man-in-the-Middle (MITM) attacks&lt;/a&gt; and Traceability respectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;Let's take a look at how Onion Routing works. In Onion Routing, a message is encapsulated with multiple layers of encryption, analogous to the layers of an onion, this message is then passed through a series of network nodes/relay servers known as "onion routers" that peel of a layer of encryption one by one at each node until the message arrives at the destination server. This mechanism provides confidentiality. THe diagram below shows how these encryption layers are peeled off at different nodes in the network&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%2F90hcs8zr7wbc866af805.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%2F90hcs8zr7wbc866af805.png" alt="Tor Circuit Diagram" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a client sends a network request over the Tor network through the Tor Browser (The web browser used to browse the Tor Network). The browser first sends the request to a gateway node known as the Entry Guard. This is the Entry Point into the Tor Network, however the Entry guard is not aware that it is the Entry point of the network and also whether the message came from another relay node or from a client. The Entry guard then connects to another relay called Middle Relay which also connects to another Middle Relay. The last relay in the chain is the Exist Relay which connects to the website. Each relay knows only the previous system and the next one, but never the whole chain. In the point of view of the website, it's being accessed through Tor and is aware of the address of the Exit Relay but cannot know anything else beyond that. This chaining of relay servers is what enables Tor to eliminate traceability.&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%2Fqio3om0kwcepqq1o5dp4.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%2Fqio3om0kwcepqq1o5dp4.png" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One last thing we need to know when accessing a hidden service is the Onion Address. Unlike normal websites which we access using their URLs, hidden services are accessed using a special type of addresses called onion addresses. Each Onion Address contains an opaque non-mnemonic string of 16 characters followed by a special TLD (top level domain) address of .onion. The .onion addresses are not part of the typical DNS on the Internet hence are not accessible over traditional internet clients. THis is one of the reasons why Tor has its own special browser. Here is an example of an Onion Address: &lt;a href="http://zsuwmakpr3gfryt6.onion" rel="noopener noreferrer"&gt;http://zsuwmakpr3gfryt6.onion&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying on the Tor Network
&lt;/h2&gt;

&lt;p&gt;Now that we have a general understanding of how the Tor network works, let's move on to deploying a simple website to the Tor Network. We shall do this by following a series of steps. Before continuing ensure you have a Linux server or Virtual Machine running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Tor Services
&lt;/h3&gt;

&lt;p&gt;To use the Tor network, you are required to have two main components, The Tor Browser and the Tor Server. The Tor browser can be installed on almost all platforms, head over to &lt;a href="https://www.torproject.org/download/" rel="noopener noreferrer"&gt;the Tor Download page to download it for your platform&lt;/a&gt;. On the other hand, as far as I know The Tor server is a linux program so you are required to have a linux Instance running. If you're using a debian system run the following commands to install it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get update
apt-get &lt;span class="nb"&gt;install &lt;/span&gt;tor
apt-get &lt;span class="nb"&gt;install &lt;/span&gt;torbrowser-launcher
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting up your Web Application/Server
&lt;/h3&gt;

&lt;p&gt;Depending on the tech stack of your application or website, you will be required to start your website on the local host address (127.0.0.1) listening at a specific port, for example port &lt;code&gt;3000&lt;/code&gt;. Ensure that the port you're listening to is not exposed outside the server as this will make your site accessible over the Clearnet hindering the anonymity we are aiming for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the Website to Tor
&lt;/h3&gt;

&lt;p&gt;Now that we have our web server listening on localhost, we can configure our Tor program to designate this server as a Tor hidden service. We do this by editing Tor configuration file &lt;code&gt;/etc/tor/torrc&lt;/code&gt; as follows:&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="c"&gt;############### This section is just for location-hidden services ###&lt;/span&gt;

&lt;span class="c"&gt;## Once you have configured a hidden service, you can look at the&lt;/span&gt;
&lt;span class="c"&gt;## contents of the file ".../hidden_service/hostname" for the address&lt;/span&gt;
&lt;span class="c"&gt;## to tell people.&lt;/span&gt;
&lt;span class="c"&gt;##&lt;/span&gt;
&lt;span class="c"&gt;## HiddenServicePort x y:z says to redirect requests on port x to the&lt;/span&gt;
&lt;span class="c"&gt;## address y:z.&lt;/span&gt;

HiddenServiceDir /var/lib/tor/hidden_service/
HiddenServicePort 80 127.0.0.1:3000

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you open the &lt;code&gt;torrc&lt;/code&gt; file, you will find the lines &lt;code&gt;HiddenServiceDir&lt;/code&gt; and &lt;code&gt;HiddenServicePort&lt;/code&gt; commented, so you will have to uncomment them and also modify the &lt;code&gt;HiddenServicePort&lt;/code&gt; to make sure that it points to the port at which you application is listening.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the Tor Service
&lt;/h3&gt;

&lt;p&gt;After setting up the required configuration, we can then run the Tor Service to perform all necessary actions and register our site to the Tor network. It will also create private and public keys for encryption and also create an onion address for your site. To run the Tor Service, simply run the command &lt;code&gt;tor&lt;/code&gt;&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;$ &lt;/span&gt;tor

Nov 13 17:18:09.832 &lt;span class="o"&gt;[&lt;/span&gt;notice] Tor 0.3.4.8 &lt;span class="o"&gt;(&lt;/span&gt;git-5da0e95e4871a0a1&lt;span class="o"&gt;)&lt;/span&gt; running on Linux with Libevent 2.1.8-stable, OpenSSL 1.1.0h, Zlib 1.2.8, Liblzma 5.2.2, and Libzstd 1.3.5.
Nov 13 17:18:09.851 &lt;span class="o"&gt;[&lt;/span&gt;notice] Tor can’t &lt;span class="nb"&gt;help &lt;/span&gt;you &lt;span class="k"&gt;if &lt;/span&gt;you use it wrong! Learn how to be safe at https://www.torproject.org/download/download#warning
Nov 13 17:18:09.947 &lt;span class="o"&gt;[&lt;/span&gt;notice] Read configuration file “/etc/tor/torrc”.
…
…
…
Nov 13 17:18:36.000 &lt;span class="o"&gt;[&lt;/span&gt;notice] Bootstrapped 90%: Establishing a Tor circuit
Nov 13 17:18:37.000 &lt;span class="o"&gt;[&lt;/span&gt;notice] Tor has successfully opened a circuit. Looks like client functionality is working.
Nov 13 17:18:37.000 &lt;span class="o"&gt;[&lt;/span&gt;notice] Bootstrapped 100%: Done

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use a service like &lt;code&gt;nohup&lt;/code&gt; inorder to run it as a background process&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="nb"&gt;nohup &lt;/span&gt;tor &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Tor program will create two files under the directory &lt;code&gt;/var/lib/tor/hidden_services/&lt;/code&gt; which include the Private Key and a &lt;code&gt;hostname&lt;/code&gt; file containing the onion address of your site. Ensure to keep the Private Key secure and confidential.&lt;/p&gt;

&lt;p&gt;You can now get your onion address by reading the contents of the &lt;code&gt;hostname&lt;/code&gt; file&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="nb"&gt;cd&lt;/span&gt; /var/lib/tor/hidden_service/

&lt;span class="nb"&gt;cat hostname

&lt;/span&gt;zjsytjmah6hiyw1h.onion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, open the Tor Browser and head to the onion address provided and you will be able to access your anonymous site.&lt;/p&gt;

&lt;p&gt;The Dark Web is often known for the illicit Activities that are carried out over it. This has portrayed the Dark Web as a dangerous place on the Internet. However, the Dark Web is used for more than that. Law enforcement agencies and cybersecurity professionals use Tor for undercover operations, intelligence gathering, and security research to combat cybercrime, terrorism, and fraud. Researchers, companies, and individuals dealing with sensitive topics (such as cybersecurity, medical research, or financial transactions) use Tor to protect trade secrets, proprietary data, and confidential communications from cyber threats and corporate espionage. Human rights activists and political dissidents use Tor to evade surveillance and protect themselves from oppressive governments that seek to suppress dissent. This fosters democracy and civil liberties worldwide.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>An Introduction to Supervised Learning Models</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Sat, 08 Mar 2025 14:21:29 +0000</pubDate>
      <link>https://forem.com/jimjunior/an-introduction-to-supervised-learning-models-2df2</link>
      <guid>https://forem.com/jimjunior/an-introduction-to-supervised-learning-models-2df2</guid>
      <description>&lt;p&gt;Machine learning is a subset of AI that learns to make decisions by fitting mathematical models to observed data. This area has seen explosive growth and is now (incorrectly) almost synonymous with the term AI. Machine learning models are generally categorized into two. Supervised and Unsupervised learning models. In this article we shall take a look at Supervised Learning models, how they work and different concepts associated with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Supervised Learning
&lt;/h2&gt;

&lt;p&gt;Supervised learning is a field of machine learning where mathematical models are trained on labeled data from datasets using the input and expected output inorder for the model to learn the relationship between the input and output. That aim is for the models to be able to predict data from new unseen data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems in Supervised learning
&lt;/h2&gt;

&lt;p&gt;Problems in Supervised learning are usually classified into two kinds. Supervised learning models primarily address two types of problems. Regression and Classification models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regression problems&lt;/strong&gt; are problems that require a model that predicts continuous values. Think of a problem which requires us to predict the price of a house, we are to create a model that takes inputs which are various characteristics like location, size etc, and from these inputs we predict a price. We can also have problems that predict more than one value and these are called multivariable regression problems. Take an example of a model that predicts the age and height of a tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classification Problems&lt;/strong&gt; on the other hand are problems that produce a categorical output, for example a model that predicts if it will rain or not. This is actually called a binary Classification problem since the output has only two states to exist. However, the model can predict an output that can be in one of more than 2 possible categories. These are called multiclass classification problems. Take an example of a model that predicts what animal is in a picture, and the animal can be anything from a dog, cat, cow, goat etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supervised Learning Models
&lt;/h2&gt;

&lt;p&gt;In supervised learning, we aim to build models that take an input 

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;xx&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 and predict an output 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;yy&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
. The model is a mathematical equation 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;f[•]f[•] &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;f&lt;/span&gt;&lt;span class="mopen"&gt;[&lt;/span&gt;&lt;span class="mord"&gt;•&lt;/span&gt;&lt;span class="mclose"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
  that takes inputs 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;xx&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 and predicts 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;yy&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
. We can then represent the output as 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;y=f(x)y = f(x) &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;f&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 .&lt;/p&gt;

&lt;p&gt;This process of computing a prediction 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;yy&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 from 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;xx&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 through the model is called &lt;strong&gt;inference&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model is a fixed equation that contains parameters 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;θ\theta&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, the choice of parameters 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;θ\theta&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 determines the relationship between the input and prediction. Therefore the prediction 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;yy&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 varies based on the parameters. We can then rewrite the equation to support the parameters 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;θ\theta&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 as.&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;y=f[x,θ]
 y = f[x,\theta]
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;f&lt;/span&gt;&lt;span class="mopen"&gt;[&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;span class="mclose"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;When we say we are &lt;em&gt;training a model&lt;/em&gt;, we are feeding the models with input output pairs 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;(x,y)(x, y)&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 and aim to find the parameters 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;θ\theta&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 that match inputs 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;xx&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 to outputs that are as close as possible to the expected output 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;yy&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
.&lt;/p&gt;

&lt;p&gt;The mismatch between the output and expected output is called the &lt;em&gt;loss&lt;/em&gt; 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;LL&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;L&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
. This loss is a scalar value that describes how poorly our model predicts the expected output. We can treat the loss as a function 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;L(θ)L(\theta)&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;L&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 of the parameters 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;θ\theta&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
. Generally we are training the model to find the parameters that minimize the loss as possible.&lt;/p&gt;

&lt;p&gt;After training a model, we must now assess its performance; we run the model on separate test data to see how well it generalizes to examples that it didn’t observe during training. If the performance is adequate, then we are ready to deploy the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model Inputs
&lt;/h3&gt;

&lt;p&gt;Learning models are usually mathematical equations that take inputs in numerical form, but in the real world, these inputs appear in a variety of formats, so we have to find a way to encode them into numerical form.&lt;/p&gt;

&lt;p&gt;Inputs and how they are interpreted can vary in different forms, for example if we are predicting the price of a house, the inputs are location, number of bedrooms etc and it doesn't matter the order in which they are input, the model is expected to predict the same price. Now take an example of a model that predicts if a sentence is realistic or not, the sentences, &lt;em&gt;"my wife ate the chicken"&lt;/em&gt; and &lt;em&gt;"the chicken ate my wife"&lt;/em&gt; are the same in terms of the words they consist of but the order is different hence the meaning and the expected model prediction is different.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model Outputs
&lt;/h3&gt;

&lt;p&gt;The output of the model also varies wildly depending on the type of the problem. Although we said the problems being solved are regression or classification problems, the output in real world applications is not usually a single value (as in classification or regression) but a structured object, such as a sequence, tree, or graph. For example a Speech recognition model that transforms audio waveforms to a sequence of words.&lt;/p&gt;

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

&lt;p&gt;This is just an overview of Supervised learning models, in the next article,  we shall look at a Practical example using Linear Regression. However, if you want to learn more about supervised learning models, I would recommend Reading Chapter 2 and 3 of the &lt;a href="https://udlbook.github.io/udlbook/" rel="noopener noreferrer"&gt;Understanding Deep Learning Book&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for taking the time to read this article. I hope it has been informative. About me, I am &lt;a href="https://jim-junior.github.io/" rel="noopener noreferrer"&gt;Beingana Jim Junior&lt;/a&gt;, a software engineering student at Makerere University. Check out my &lt;a href="https://github.com/jim-junior" rel="noopener noreferrer"&gt;Github&lt;/a&gt; and &lt;a href="https://jim-junior.github.io/" rel="noopener noreferrer"&gt;Portifolio website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to share your thoughts, questions, or feedback&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>deeplearning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A Practical Introduction to the Event Driven Architecture</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Sat, 18 Jan 2025 14:03:41 +0000</pubDate>
      <link>https://forem.com/jimjunior/a-practical-introduction-to-the-event-driven-architecture-dol</link>
      <guid>https://forem.com/jimjunior/a-practical-introduction-to-the-event-driven-architecture-dol</guid>
      <description>&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%2Fxw32xdi7gv4wbpyquldi.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%2Fxw32xdi7gv4wbpyquldi.png" alt="Event Driven Architecture" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After working extensively with microservices, I’ve come to appreciate the power and flexibility of the &lt;strong&gt;Event-Driven Architecture (EDA)&lt;/strong&gt;. EDA is a software design pattern that allows systems to detect, process, and react to real-time events in a decoupled manner. It is particularly suited for microservices, enabling seamless communication and interaction between independent components.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore the principles of event-driven architecture, discuss its core concepts, and demonstrate how it works using a custom-built command-line tool. This tool, written in Go and powered by Redis, serves as a practical example of EDA in action.&lt;/p&gt;

&lt;p&gt;To follow along with the demonstrations, you’ll need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/jim-junior/eda/releases" rel="noopener noreferrer"&gt;Download the CLI tool from GitHub&lt;/a&gt; (available for Windows, Linux, and macOS) Make sure to download the latest release.&lt;/li&gt;
&lt;li&gt;Have a Redis server running locally. You can install Redis from the official website or use Docker for easier setup (recommended):&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the Event-Driven Architecture Works
&lt;/h2&gt;

&lt;p&gt;At a high level point of view the EDA is quite straight forward. Events representing occurrences or changes in the system drive the flow are generated by various sources (Publishers), published to an event bus or message broker, and consumed by interested components (Consumers) asynchronously. This approach promotes flexibility, scalability, and resilience.&lt;/p&gt;

&lt;p&gt;An event is any change in state of the system and can be anything ranging from customer requests, inventory updates, sensor readings, and so on.&lt;/p&gt;

&lt;p&gt;The event-driven architecture enables different components of a system to run independently of each other by introducing a middleman known as an &lt;strong&gt;Event broker&lt;/strong&gt; that routes events to different intended destination components. This means that applications and devices do not need to know where they are sending information or where the information they are consuming comes from. Rather they focus on carrying out there intended functionality.&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%2F5nttalbkp34fb92d1i9e.jpg" 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%2F5nttalbkp34fb92d1i9e.jpg" alt="Image showing producer" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: In this article we use Redis because of its simplicity and realatively easy learning curve but Redis wont demonstrate the full functionality of an Event Driven System, however there are other more complex software systems such as RabbitMQ or Apache Kafka that provide a wide range of functionality and Protocals such as AMQP and MQTT(Commonly used in IoT application) that can be used to build more large scale systems, however these have a steep learning curve and wont be ideal for demonstration purposes in this article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Concepts
&lt;/h2&gt;

&lt;p&gt;With this high level understanding of how the Event-Driven Architecture works, lets look and some of its core concepts. These will give you a platform to weigh the strengths of this architecture to others and possibly give you a basis on deciding when to apply it in your projects. We sha;; use the tool to demonstrate these concepts.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Event broker
&lt;/h3&gt;

&lt;p&gt;An &lt;em&gt;Event Broker&lt;/em&gt; is a middleware platform that routes events from source to desired destination. This is accomplished using the publish-subscribe messaging pattern. In this pattern, independent system components connect to the event broker and exchange messages through the Event Broker.&lt;/p&gt;

&lt;p&gt;Multiple event brokers Exist, some of the most popular ones include Apache Kafka, RabbitMQ, AWS EventBridge, and Redis. In this article, we will use Redis as our event broker. To follow along with the practical example atart you redis instance. Make sure its listening on port &lt;code&gt;6379&lt;/code&gt; as thats where &lt;code&gt;eda&lt;/code&gt; cli tool will try to access it from. Unfortunately I had to dard code the port value. You can run the following command to run Redis in a Docker Container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 6379:6379 redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Event Portals
&lt;/h3&gt;

&lt;p&gt;Event portals in Event-Driven Architecture are tools or platforms designed to facilitate the management, discovery, and governance of events within an event-driven system. They are especially valuable in modern distributed systems where events act as the primary means of communication between components.&lt;/p&gt;

&lt;p&gt;You should not confuse them with Event Brokers as the definition might sound similar. Here are the differences&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Event Portal&lt;/th&gt;
&lt;th&gt;Event Broker&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Function&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Discovery, governance, and design of events&lt;/td&gt;
&lt;td&gt;Real-time routing and delivery of events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Focus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Design-time and metadata management&lt;/td&gt;
&lt;td&gt;Runtime event processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Entire event-driven system&lt;/td&gt;
&lt;td&gt;Specific event transportation tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AsyncAPI, Solace Event Portal&lt;/td&gt;
&lt;td&gt;Kafka, RabbitMQ, AWS EventBridge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I found a good article on the Internet explaining Event Portals in detail. &lt;a href="https://solace.com/what-is-an-event-portal/" rel="noopener noreferrer"&gt;Read it here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Mesh
&lt;/h3&gt;

&lt;p&gt;An event mesh is created and enabled through a network of interconnected event brokers. It’s a configurable and dynamic infrastructure layer for distributing events among decoupled applications, cloud services, and devices by dynamically routing events to any application, no matter where these applications are deployed in the world, in any cloud, on-premises, or IoT environment. Technically speaking, an event mesh is a network of interconnected event brokers that share consumer topic subscription information and route messages amongst themselves so they can be passed along to subscribers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Topics in Event-Driven Architecture&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In an event-driven system, &lt;strong&gt;topics&lt;/strong&gt; are labels used to organize and route events. When an event is published, it is associated with a specific topic, and consumers subscribe to topics to receive events relevant to them. This approach allows producers to send events without needing to know which consumers will process them, while consumers only handle events tied to their subscribed topics. For example, an event with the topic &lt;code&gt;orderCreated&lt;/code&gt; could be published by a producer, and any consumers subscribed to &lt;code&gt;orderCreated&lt;/code&gt; would receive and process the event.&lt;/p&gt;

&lt;p&gt;In this demonstration, we will set up a system with two components: the API server, which acts as the producer, and the consumer, which listens to a topic and logs the events it receives. First, you’ll need to start the API server by running the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eda api-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command initializes the server, which listens for POST requests. These requests include events that the server will publish to Redis, using the specified topic. Next, you’ll start a consumer that subscribes to a particular topic. To subscribe to the topic &lt;code&gt;orderCreated&lt;/code&gt;, use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eda consumer &lt;span class="nt"&gt;--topic&lt;/span&gt; orderCreated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With both the API server and the consumer running, you can now send an event to the system. Use an HTTP client, such as &lt;code&gt;curl&lt;/code&gt; or Postman, to send a POST request to the API server. For instance, sending the following request publishes an event to the topic &lt;code&gt;orderCreated&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"order_id": 123, "customer": "John Doe", "total": 49.99}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
http://localhost:3000/topics/orderCreated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this request is received, the API server sends the event to Redis, the event broker. Redis routes the event to all consumers subscribed to the topic &lt;code&gt;orderCreated&lt;/code&gt;. If a consumer is actively listening, it will receive the event and log the message to its console. The consumer’s output will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Received event on topic: orderCreated
Event data: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"order_id"&lt;/span&gt;: 123, &lt;span class="s2"&gt;"customer"&lt;/span&gt;: &lt;span class="s2"&gt;"John Doe"&lt;/span&gt;, &lt;span class="s2"&gt;"total"&lt;/span&gt;: 49.99&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This demonstration shows how topics allow events to be routed to the appropriate consumers without requiring direct communication between the producer and the consumers. By associating events with topics, the system achieves flexibility and scalability while keeping its components decoupled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deferred Execution
&lt;/h3&gt;

&lt;p&gt;Deferred execution is a key concept in event-driven architecture that differs significantly from traditional REST-based APIs. In a REST-based system, when you send a request, you typically wait for an immediate response. However, in event-driven systems, you don’t wait for a response when you publish an event. Instead, the event is handed off to an event broker, which holds or persists the event until consumers are ready to process it. This allows for more flexible and decoupled communication, as consumers can process events at their own pace, potentially much later.&lt;/p&gt;

&lt;p&gt;In this approach, acting on one event might also cause new events to be generated and persisted in the event broker. These subsequent events can trigger further actions in other parts of the system, creating a chain of reactions, all while maintaining the flexibility of deferred processing.&lt;/p&gt;

&lt;p&gt;To understand this concept better, let’s walk through a practical demonstration of deferred execution.&lt;/p&gt;

&lt;h4&gt;
  
  
  Practical Demonstration
&lt;/h4&gt;

&lt;p&gt;In this demonstration, you will observe how events can be held in a queue and processed after a delay, demonstrating deferred execution. Here are the steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start the API Server&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, start the API server, which allows you to publish events to the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eda api-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This server listens for POST requests and routes event messages to the system.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Send an Event&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Send a POST request to the &lt;code&gt;/deferred-exec&lt;/code&gt; endpoint of the API server. The request body contains the event message you want to publish. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"task_id": 456, "description": "Process data files"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
http://localhost:3000/deferred-exec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this request is sent, the API server pushes the event to Redis, the event broker, where it will be stored in a queue for processing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start the Master Node&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, start the master node, which plays a key role in deferred execution by sending events from Redis to the event queue. To start the master node, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eda consumer &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start a Consumer&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, run the consumer to process events from the event queue. The consumer checks the queue every 10 seconds, retrieves the events, and logs them to demonstrate deferred execution. To start the consumer, use the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eda consumer &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the consumer is running, you will see a 10-second delay before the consumer processes events. For example, the consumer’s log might display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Processing event: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"task_id"&lt;/span&gt;: 456, &lt;span class="s2"&gt;"description"&lt;/span&gt;: &lt;span class="s2"&gt;"Process data files"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This delay illustrates how events can be stored in a queue and processed later, at the consumer’s convenience. Such deferred execution is particularly useful in scenarios where immediate processing isn’t required, allowing for greater flexibility and resource efficiency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eventual Consistency
&lt;/h3&gt;

&lt;p&gt;Building on the concept of deferred execution, where events are processed at a later time rather than immediately, we encounter the idea of eventual consistency. In an event-driven system, since events are not processed instantly, there’s no guarantee that all components, such as databases or systems, are synchronized at any given moment. For example, if you have multiple systems—like a database, a master data management (MDM) system, and an ERP—they may not have the same state at the same time.&lt;/p&gt;

&lt;p&gt;In our deferred execution demonstration, when an event (like a task or an order) is published to Redis, it may take time for the consumer to process it. Until the consumer acts on the event and updates the relevant systems, those systems might not reflect the latest state. However, while consistency isn’t immediate, we can rely on the system to eventually synchronize. This means that all stateful components will reach a consistent state over time, provided the system is functioning as intended and events are processed correctly.&lt;/p&gt;

&lt;p&gt;For example, after publishing a task event with the API server, the consumer may process it after a delay, updating the system state accordingly. While the task's status may appear outdated immediately after publication, you can trust that the system will catch up and reflect the changes eventually.&lt;/p&gt;

&lt;p&gt;This principle is particularly useful in distributed systems, where maintaining immediate consistency across all components can be impractical or inefficient. By embracing eventual consistency, we trade immediate synchronization for better scalability and resilience, trusting the system to converge to a consistent state in due time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choreography
&lt;/h3&gt;

&lt;p&gt;In the world of event-driven architecture, &lt;strong&gt;choreography&lt;/strong&gt; is a pattern where multiple components interact and coordinate their actions by responding to shared events. Unlike orchestration, where a central controller dictates the flow of operations, choreography allows each component to act independently based on the events it receives. This decentralized approach aligns well with the principles of EDA, promoting loose coupling and scalability.  &lt;/p&gt;

&lt;h4&gt;
  
  
  Benefits of Choreography
&lt;/h4&gt;

&lt;p&gt;Choreography ensures that components operate independently, reducing the risk of bottlenecks or single points of failure. In our system, the master node facilitates event flow but does not tightly couple itself to the consumers. This allows for easy scaling—new consumers can join without the master node needing to know about them, as long as they subscribe to the appropriate topics or queues.  &lt;/p&gt;

&lt;p&gt;By adopting choreography, you can build systems that are more resilient and flexible. Each component focuses on its responsibilities, reacting to events as needed, rather than relying on a central controller to orchestrate every action.  &lt;/p&gt;

&lt;h3&gt;
  
  
  CQRS:Command Query Responsibility Segregation
&lt;/h3&gt;

&lt;p&gt;CQRS is a method for scaling microservices by separating responsibilities. One service handles commands, such as updates or inserts, while another handles queries, such as fetching data. This separation is useful because query services often need to handle far more requests than commands, making it easier to scale the query service independently.&lt;/p&gt;

&lt;p&gt;In an event-driven architecture, implementing CQRS is straightforward. Events in the system can be categorized by topics that include a specific action or verb, like "query" or "update." To scale the query service, you can simply deploy more instances of it and have them listen to the topics related to queries. This way, the system remains efficient, scalable, and easy to manage.&lt;/p&gt;

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

&lt;p&gt;Event-Driven Architecture (EDA) is a powerful paradigm that enables systems to be more flexible, scalable, and resilient by decoupling components and allowing them to communicate through events. Throughout this article, we’ve explored the principles of EDA, discussed its core concepts like deferred execution, eventual consistency, choreography, and CQRS, and demonstrated how these concepts can be applied in practice using Redis and a custom-built CLI tool.&lt;/p&gt;

&lt;p&gt;By embracing EDA, you can design systems that not only handle complex workflows efficiently but also adapt to changing demands with ease. Whether you're scaling query services, coordinating actions through events, or ensuring eventual consistency in distributed systems, EDA provides the tools and patterns to build robust architectures.&lt;/p&gt;

&lt;p&gt;Thank you for taking the time to read this article! I hope it has been informative and inspires you to experiment with EDA in your own projects. About me, I am &lt;a href="https://jim-junior.github.io/" rel="noopener noreferrer"&gt;Beingana Jim Junior&lt;/a&gt;, a software engineering student at Makerere University, I love sharing insights from my learning and experiences. I’m passionate about software design and always eager to explore new technologies and solutions. Check out my &lt;a href="https://github.com/jim-junior" rel="noopener noreferrer"&gt;Github&lt;/a&gt; and &lt;a href="https://jim-junior.github.io/" rel="noopener noreferrer"&gt;Portifolio website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to share your thoughts, questions, or feedback. I’d love to hear from you. Let’s continue building amazing systems together!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Event-Driven Architecture. Amazon Web Services. &lt;a href="https://aws.amazon.com/event-driven-architecture/" rel="noopener noreferrer"&gt;https://aws.amazon.com/event-driven-architecture/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Event-Driven Architecture (EDA) - A Complete Introduction. Confluent. &lt;a href="https://www.confluent.io/learn/event-driven-architecture/" rel="noopener noreferrer"&gt;https://www.confluent.io/learn/event-driven-architecture/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Complete Guide to Event-Driven Architecture. Medium. &lt;a href="https://medium.com/@seetharamugn/the-complete-guide-to-event-driven-architecture-b25226594227" rel="noopener noreferrer"&gt;https://medium.com/@seetharamugn/the-complete-guide-to-event-driven-architecture-b25226594227&lt;/a&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>softwaredevelopment</category>
      <category>learning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>My gift to 2024</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Sat, 28 Dec 2024 17:18:14 +0000</pubDate>
      <link>https://forem.com/jimjunior/my-gift-to-2024-5doc</link>
      <guid>https://forem.com/jimjunior/my-gift-to-2024-5doc</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/jimjunior" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F539256%2F4c93693c-d5ba-4723-aab5-1a6947f451b9.jpg" alt="jimjunior"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/jimjunior/building-a-kubernetes-operator-a-practical-guide-2lna" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building a Kubernetes Operator | A Practical Guide&lt;/h2&gt;
      &lt;h3&gt;Beingana Jim Junior ・ Dec 28 '24&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#kubernetes&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>kubernetes</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Building a Kubernetes Operator | A Practical Guide</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Sat, 28 Dec 2024 17:17:17 +0000</pubDate>
      <link>https://forem.com/jimjunior/building-a-kubernetes-operator-a-practical-guide-2lna</link>
      <guid>https://forem.com/jimjunior/building-a-kubernetes-operator-a-practical-guide-2lna</guid>
      <description>&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%2F43gqlpitxibuh29k48nx.jpg" 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%2F43gqlpitxibuh29k48nx.jpg" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of those great revolutionary technologies that have transformed how developers think of and Interact with cloud infrastructure is Kubernetes. Initially Developed at Google, Kubernetes, also known as K8s, is an open source system for automating deployment, scaling, and management of containerized applications. It is Designed on the same principles that allow Google to run billions of containers a week, Kubernetes can scale without increasing your operations team.&lt;/p&gt;

&lt;p&gt;Despite its remarkable capabilities, Kubernetes is fundamentally a container orchestration technology. While it greatly simplifies deployment and scaling, it doesn't address all challenges that software engineers encounter in software development and DevOps. To Solve this Kubernetes provides ways it can be Extended and customized to meet your team's needs. It Provides Client Libraries in many languages. But even better are Kubernetes Operators.&lt;/p&gt;

&lt;p&gt;A formal definition of a kubernetes operator can be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Kubernetes Operator is a method of automating the management of complex applications on Kubernetes. It extends Kubernetes' capabilities by using custom resources and controllers to manage the lifecycle of an application, automating tasks such as deployment, scaling, and updates. Operators encode the operational knowledge of an application into Kubernetes, making it easier to manage stateful or complex workloads with less manual intervention.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article. We shall go through a guide to get started building your own Custom kubernetes Operator. We shall cover different topics like Custom Resource Definitions, Controllers and look at the Kubernetes Controller Runtime.&lt;/p&gt;

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

&lt;p&gt;There are a few thing we need to know and have before continuing with this tutorial&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A good understanding of Kubernetes and how to use it.&lt;/li&gt;
&lt;li&gt;Programming Knowledge in the Go Programming Language.&lt;/li&gt;
&lt;li&gt;Access to a Kubernetes Cluster (You can try it locally using Minikube or Kind)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To set up your environment you will first need to have Go installed. The Kubernetes Golang Client usually requires a specific Go version so depending on your time of reading this article it might have changed but for now i will use &lt;code&gt;go1.21.6&lt;/code&gt;. To know what version you are using, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go version
&lt;span class="c"&gt;# Example output: go version go1.21.6 linux/amd64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next you will need to have access to a Kubernetes cluster but for development purposes I would advise you use a local cluster from tools like &lt;a href="https://minikube.sigs.k8s.io/docs/start" rel="noopener noreferrer"&gt;Minikube&lt;/a&gt; or &lt;a href="https://kind.sigs.k8s.io/" rel="noopener noreferrer"&gt;Kind&lt;/a&gt;. You can visit their websites for the installation steps.&lt;/p&gt;

&lt;p&gt;Before we dive right into code. There are certain key concepts you will need to know so let's look at those first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Custom Resource Definitions (CRDS)
&lt;/h3&gt;

&lt;p&gt;To understand CRDs you need to first know what a resource is. Pods, Deployments, Services etc are all resources. Formally&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A resource is an endpoint in the &lt;a href="https://kubernetes.io/docs/concepts/overview/kubernetes-api/" rel="noopener noreferrer"&gt;Kubernetes API&lt;/a&gt; that stores a collection of &lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/#kubernetes-objects" rel="noopener noreferrer"&gt;API objects&lt;/a&gt; of a certain kind; for example, the built-in pods resource contains a collection of Pod objects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Resources are in built within the Kubernetes API. But in our case, we said one of the main resons we build operators is to handle custom problems that Kubernetes does not solve out of the box. In some cases we might need to define our own resource objects. Forexample. Imagine we are building an operator that manages postgreSql databases, we would like to provide an API to define configurations of each database we initiate, we can do this by defining a Custom resource definition &lt;code&gt;PGDatabase&lt;/code&gt; as an Object that stores the configuration of the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of a Custom Resource Definition for demostration&lt;/strong&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example.com/v1&lt;/span&gt; &lt;span class="c1"&gt;# Every resource must have an API Version &lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PGDatabase&lt;/span&gt; &lt;span class="c1"&gt;# CRD Name&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mydb&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
    &lt;span class="na"&gt;dbname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mydb&lt;/span&gt;

  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pgvolume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/pgdata/&lt;/span&gt;

  &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress-secrets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: CRDs are just definitions of the actually objects or they just represent an actual object in the kubernetes cluster but are not the actual object. For example when you write a &lt;code&gt;yaml&lt;/code&gt; file defining how a pod should be scheduled, that &lt;code&gt;yaml&lt;/code&gt; just defines but its not the actual pod.&lt;/p&gt;

&lt;p&gt;Therefore we can define a CRD as an extension of the Kubernetes API that is not necessarily available in a default Kubernetes installation. It represents a customization of a particular Kubernetes installation. Actually, many core Kubernetes functions are now built using custom resources, making Kubernetes more modular.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controllers
&lt;/h3&gt;

&lt;p&gt;Next Lets look at Kubernetes Controllers. As we have seen above, CRDs represent objects or state of objects in the Kubernetes Cluster, but we need something to reconcile or transform that state into actual resources on the cluster, this is where Controllers come in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Kubernetes controller is a software component within Kubernetes that continuously monitors the state of cluster resources and takes action to reconcile the actual state of the cluster with the desired state specified in resource configurations (YAML files).&lt;/p&gt;
&lt;/blockquote&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%2Fqy34ptk4ic09ychk64di.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%2Fqy34ptk4ic09ychk64di.png" alt="Controller life cylce" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Controllers are responsible for managing the lifecycle of Kubernetes objects, such as Pods, Deployments, and Services. Each controller typically handles one or more resource types and performs tasks like creating, updating, or deleting resources based on a declared specification.&lt;br&gt;
Here's a breakdown of how a Kubernetes controller operates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Observe&lt;/strong&gt;: The controller watches the API server for changes to resources it's responsible for. It monitors these resources by querying the Kubernetes API periodically or via an event stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analyze&lt;/strong&gt;: It compares the actual state (current conditions of the resources) to the desired state (specified in configurations).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act&lt;/strong&gt;: If there’s a discrepancy between the actual and desired state, the controller performs operations to align the actual state with the desired state. For example, a Deployment controller might create or terminate Pods to match the specified replica count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loop&lt;/strong&gt;: The controller operates in a loop, continuously monitoring and responding to changes to ensure the system’s resources are always in the desired state.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Controller Runtime
&lt;/h3&gt;

&lt;p&gt;Kubernetes provides a set of tools to build native Controllers and these are Known as the Controller Runtime.&lt;/p&gt;

&lt;p&gt;Lets take a deep look into the Controller runtime because its what we shall be using to build out Operators&lt;/p&gt;
&lt;h2&gt;
  
  
  A Deep look into the Controller Runtime
&lt;/h2&gt;

&lt;p&gt;Controller Runtime is a set of libraries and tools in the Kubernetes ecosystem, designed to simplify the process of building and managing Kubernetes controllers and operators. It’s part of the Kubernetes Operator SDK and is widely used to develop custom controllers that can manage Kubernetes resources, including custom resources (CRDs).&lt;/p&gt;

&lt;p&gt;Controller Runtime provides a structured framework for controller logic, handling many of the lower-level details of working with the Kubernetes API, so developers can focus more on defining the behavior of the controller and less on boilerplate code. It is written in Go and builds on the Kubernetes &lt;code&gt;client-go&lt;/code&gt; libraries.&lt;/p&gt;

&lt;p&gt;To use it, you can add it to you golang project by importing it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;

&lt;span class="c"&gt;// Controller runtime packages&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime"&lt;/span&gt;
    &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/client"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The Controller runtime is not the only way one can build a Kubernetes Operator, there are multiple ways to do so such as using the &lt;a href="https://sdk.operatorframework.io/" rel="noopener noreferrer"&gt;Operator Framework SDK&lt;/a&gt; or &lt;a href="https://book.kubebuilder.io/" rel="noopener noreferrer"&gt;Kubebuilder&lt;/a&gt;, which are both frameworks built on top of the Controller runtime and utilize it under the hood to assist you when building complex Operators. You could even build an application that utilizes the Kubernetes Rest API through client libraries in various languages such as Python, Java, JavaScript etc depending on your tech stack. Find the Full list of Client Libraries on the &lt;a href="https://kubernetes.io/docs/reference/using-api/client-libraries/" rel="noopener noreferrer"&gt;Kubernetes Documentation&lt;/a&gt;.&lt;br&gt;
In this article, we will use the Controller runtime because it offers flexibility and provides a hands-on understanding of how Controllers work internally. This approach is ideal for gaining deeper insight into the inner workings of Kubernetes Operators while maintaining the ability to extend or customize as needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Key Components of Controller Runtime
&lt;/h3&gt;

&lt;p&gt;The Controller Runtime has several key components that streamline the process of building and running Kubernetes controllers. Together, these components create a robust framework for building Kubernetes controllers.&lt;/p&gt;

&lt;p&gt;The Manager initiates and manages other components; the Controller defines reconciliation logic; the Client simplifies API interactions; the Cache optimizes resource access; Event Sources and Watches enable event-driven behavior; and the Reconcile Loop ensures continuous alignment with the desired state. These components make it easier to build controllers and operators that efficiently manage Kubernetes resources, allowing for custom automation and orchestration at scale.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.  Manager
&lt;/h4&gt;

&lt;p&gt;The Manager is the main entry point of a controller or operator, responsible for initializing and managing the lifecycle of other components, such as controllers, caches, and clients. Developers usually start by creating a Manager in the main function, which acts as the foundational setup for the entire operator or controller.&lt;/p&gt;

&lt;p&gt;It provides shared dependencies (e.g., the Kubernetes client and cache) that can be reused across controllers, allowing multiple controllers to be bundled within a single process.&lt;/p&gt;

&lt;p&gt;The Manager also coordinates starting and stopping all controllers it manages, ensuring that they shut down gracefully if the Manager itself is stopped.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Controller
&lt;/h4&gt;

&lt;p&gt;The Controller is the core component that defines the reconciliation logic responsible for adjusting the state of Kubernetes resources. It is a control loop that monitors a cluster's state and makes changes to move it closer to the desired state&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F1%2Azacjsy7nznxEgFhxJFfC0w.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F1%2Azacjsy7nznxEgFhxJFfC0w.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each controller watches specific resources, whether built-in (e.g., Pods, Deployments) or custom resources (CRDs). It includes a &lt;code&gt;Reconcile&lt;/code&gt; function that’s triggered whenever a resource changes, allowing the controller to bring the current state in line with the desired state.&lt;/p&gt;

&lt;p&gt;Developers specify which resources the controller should watch, and Controller Runtime will automatically track and respond to events (like create, update, delete) for those resources. In a controller for managing custom &lt;code&gt;Foo&lt;/code&gt; resources, the &lt;code&gt;Reconcile&lt;/code&gt; function might create or delete associated resources based on Foo specifications.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Client
&lt;/h4&gt;

&lt;p&gt;When building a Kubernetes operator, you need some interface to interact with the Kubernetes Cluster and carry out your operations. Just like &lt;code&gt;kubectl&lt;/code&gt; the command line client we use, the Controller runtime provided a &lt;code&gt;client&lt;/code&gt; in its SDK tools. This client is also used to interact with the Kubernetes API pragrammatically in your code.&lt;/p&gt;

&lt;p&gt;The Client is &lt;em&gt;an abstraction that simplifies interactions with the Kubernetes API&lt;/em&gt;, enabling CRUD operations on resources.&lt;/p&gt;

&lt;p&gt;This component allows for easy creation, reading, updating, and deletion of Kubernetes resources.&lt;br&gt;
The Client is integrated with the cache to provide efficient access to resources without overloading the API server.&lt;/p&gt;

&lt;p&gt;Controller Runtime’s Client extends the basic Kubernetes &lt;code&gt;client-go&lt;/code&gt; library, making API calls more straightforward by handling details like retries and caching behind the scenes.&lt;/p&gt;

&lt;p&gt;Using the Client, developers can create a Pod directly from the controller logic with a single line of code, &lt;code&gt;client.Create(ctx, pod)&lt;/code&gt;, without having to worry about raw API requests.&lt;/p&gt;
&lt;h4&gt;
  
  
  3. Event Sources and Watches
&lt;/h4&gt;

&lt;p&gt;Event Sources and Watches determine the resources that a controller will monitor for changes, providing a way to respond to specific events in the cluster.&lt;/p&gt;

&lt;p&gt;Event Sources define what triggers the controller’s reconciliation loop, which can be based on changes in specific Kubernetes resources.&lt;br&gt;
Watches monitor these resources, allowing the controller to act on create, update, or delete events as needed.&lt;/p&gt;

&lt;p&gt;Developers can define multiple Watches for a single controller, which is useful if the controller’s behavior depends on multiple resources.&lt;/p&gt;

&lt;p&gt;A controller managing a custom &lt;code&gt;App&lt;/code&gt; resource might watch Pods, Services, and ConfigMaps, reacting to changes in any of these resources by adjusting the &lt;code&gt;App&lt;/code&gt; accordingly.&lt;/p&gt;
&lt;h4&gt;
  
  
  4. Reconcile Loop
&lt;/h4&gt;

&lt;p&gt;The Reconcile loop is the heart of the controller, implementing the main logic that determines the steps to bring resources into the desired state.&lt;br&gt;
Each controller’s Reconcile function checks the actual state of a resource and then applies necessary changes to make it conform to the desired state.&lt;br&gt;
This loop continues indefinitely, with each reconciliation acting as a self-healing mechanism to correct any divergence from the specification.&lt;br&gt;
The Reconcile loop is usually idempotent, meaning it can be repeated without causing unintended side effects, ensuring consistency even with frequent updates.&lt;br&gt;
In a Reconcile function, the controller might find that a Deployment lacks the specified number of replicas, so it updates the Deployment configuration to match the desired replica count.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Practical Example
&lt;/h2&gt;

&lt;p&gt;Now that you have grasped a few required concepts, lets take a practical approach by building a simple kubernetes oparator and deploying it on our Kubernetes Cluster. This will help you to apply the concepts above and understand the process more.&lt;/p&gt;

&lt;p&gt;In this section, we will build a simple Kubernetes operator that helps deploy applications using a custom resource definition (CRD). This operator will automate the creation of Deployments and Services based on the application configuration provided in the custom resource. By the end, you'll have a working operator deployed on a Kubernetes cluster and understand its key components.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;All code will be made available on Github at &lt;a href="https://github.com/jim-junior/crane-operator" rel="noopener noreferrer"&gt;https://github.com/jim-junior/crane-operator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;I will ensure code is commented in detail to ensure you can easily read and understand it&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Operator Requirements
&lt;/h3&gt;

&lt;p&gt;Our goal is to minimize the complexity of defining Kubernetes resources for deploying an application. Instead of writing multiple YAML files for Deployments, Services, etc., we will define a single CRD Application that encapsulates all required configurations. The operator will then create the necessary Kubernetes resources automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements for the Example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The operator shall define a CRD &lt;em&gt;Application&lt;/em&gt; that includes all the configuration of the application.&lt;/li&gt;
&lt;li&gt;From the CRD, it should create the associated Kubernetes resources such and Deployments, Services&lt;/li&gt;
&lt;li&gt;We shall Implement a controller to reconcile the state of &lt;em&gt;Application&lt;/em&gt; resources.&lt;/li&gt;
&lt;li&gt;Use the controller to create and manage &lt;em&gt;Deployments&lt;/em&gt; and &lt;em&gt;Services&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have a high level understanding of what our Operator is supposed to do and accomplish, lets setup our project and get started building the operator.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up the Project
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, we will use the Go programming language for this tutorial. Why Go? Primarily because the Controller runtime, as well as Kubernetes itself, is built using Go. Additionally, Go is specifically designed with features that make it ideal for building cloud-native applications. Its architecture ensures that your Operator will be well-suited to scale efficiently in cloud environments. We are using &lt;code&gt;go1.21.6&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So lets begin by initialising out Go project:&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="nb"&gt;mkdir &lt;/span&gt;app-operator &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;app-operator

go mod init github.com/jim-junior/crane-operator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We shall then install the go dependencies that we shall use in our project. You can install then by running this in the command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get sigs.k8s.io/controller-runtime
go get k8s.io/apimachinery
go get k8s.io/client-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next lets setup our project file/directory structure. Depending on your development paradigm you can choose one that favours you but for now we shall use this simple that i have found to work in most Operator projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app-operator/
├── api/                  # Contains CRD definitions
├── cmd/controller/         
    ├──reconciler.go      # Contains the reconciliation logic
├── main.go               # Entry point for the operator
├── config/               # Configuration files for testing and deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Defining the Custom Resource Definition (CRD)
&lt;/h3&gt;

&lt;p&gt;As metioned above, the CRD defines the specification of how our Object will look like. From familiarity with deploying multiple applications or web services, there are a few things an application needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A port to expose your application&lt;/li&gt;
&lt;li&gt;A Volume, incase you want to Persist Data&lt;/li&gt;
&lt;li&gt;A Container Image for application distribution and deployment&lt;/li&gt;
&lt;li&gt;Enviroment Variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since this is just an example we shall keep the requirements as minimal as possible, its not like we are building some operator to be used in an actual Organisation. Plus I dont want the code to become huge.&lt;/p&gt;

&lt;p&gt;When defining a Custom Resource Definition (CRD), it must be specified in two formats. The first is as a &lt;code&gt;yaml&lt;/code&gt; or &lt;code&gt;json&lt;/code&gt; OpenAPI specification (&lt;code&gt;yaml&lt;/code&gt; is preffered is its more human readable), which can be applied using &lt;code&gt;kubectl apply&lt;/code&gt; to install the CRD on a Kubernetes cluster. The second is as a Go language specification, used in your code to define and interact with the CRD programmatically.&lt;/p&gt;

&lt;p&gt;While tools like Operator SDK and Kubebuilder can automatically generate one or both of these formats for you, it's important for a developer building a Kubernetes Operator to understand the configurations being generated. This knowledge is invaluable for debugging or handling custom scenarios that may arise.&lt;/p&gt;

&lt;p&gt;Lets begin first with an example showing how we would like our CRD to look like. This will help use comeup with a Specification. Incase you dont understand some aspects of it, dont worry, we shall look into everything.&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;operator.com/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:9.0&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3306&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3306&lt;/span&gt;

  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;volume-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/&lt;/span&gt;

  &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql-secrets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we define the Open API Specification, lets look at what each component means in out CRD above.&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;operator.com/v1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines the Version of your resource. Every resource on kubernetes &lt;strong&gt;MUST&lt;/strong&gt; have an api version. Even inbuilt kubernetes resources have. I would recommend versioning your resources while following the Kubernetes API versioning convetions.&lt;/p&gt;

&lt;p&gt;Kubernetes API versioning follows conventions that reflect the maturity and stability of an API. Here’s a list of the common versioning conventions and what each represents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alpha&lt;/strong&gt; (e.g., &lt;em&gt;v1alpha1&lt;/em&gt;): Experimental and not stable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beta&lt;/strong&gt; (e.g., &lt;em&gt;v1beta1&lt;/em&gt;): More stable than alpha but still under active development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable&lt;/strong&gt; (e.g., &lt;em&gt;v1&lt;/em&gt;): Fully stable and backward-compatible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kubernetes APIs use a convention of &lt;code&gt;&amp;lt;group&amp;gt;/&amp;lt;version&amp;gt;&lt;/code&gt; (e.g., &lt;code&gt;apps/v1&lt;/code&gt;), where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Group&lt;/strong&gt;: The API group (e.g., &lt;em&gt;apps&lt;/em&gt;, &lt;em&gt;batch&lt;/em&gt;, &lt;em&gt;core&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version&lt;/strong&gt;: The maturity level of the API (&lt;em&gt;v1alpha1&lt;/em&gt;, &lt;em&gt;v1beta1&lt;/em&gt;, &lt;em&gt;v1&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Custom Resource Definitions (CRDs) follow the same versioning principles as core Kubernetes APIs.&lt;/p&gt;

&lt;p&gt;Lets move on to.&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;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Kubernetes, the kind field in a resource manifest specifies the type of resource being defined or manipulated. It is a crucial identifier that tells Kubernetes which resource object the YAML or JSON file represents, enabling the API server to process it accordingly. &lt;a href="https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" rel="noopener noreferrer"&gt;Learn more here&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The value is case-sensitive and typically written in PascalCase (e.g., &lt;code&gt;ConfigMap&lt;/code&gt;, &lt;code&gt;Deployment&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next is&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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;.....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where you define the properties of your CRD.&lt;/p&gt;

&lt;h4&gt;
  
  
  Open API Specification
&lt;/h4&gt;

&lt;p&gt;We can now define the Open API Specification for the CRD. You basically have to transform the above &lt;code&gt;yaml&lt;/code&gt; into an Open API Specification. You can learn more about &lt;a href="https://swagger.io/specification/" rel="noopener noreferrer"&gt;Open API Specification on its Documentation&lt;/a&gt;. But its pretty straight forward. For the above CRD this is what it would look like.&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apiextensions.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CustomResourceDefinition&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;applications.operator.com&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;operator.com&lt;/span&gt;
  &lt;span class="na"&gt;names&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
    &lt;span class="na"&gt;plural&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;applications&lt;/span&gt;
    &lt;span class="na"&gt;singular&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespaced&lt;/span&gt;
  &lt;span class="na"&gt;versions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
    &lt;span class="na"&gt;served&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;storage&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;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;openAPIV3Schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;can&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;provide&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;description'&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
          &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
            &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="c1"&gt;# image name&lt;/span&gt;
              &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
              &lt;span class="c1"&gt;# Volumes&lt;/span&gt;
              &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;array&lt;/span&gt;
                &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
                  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;volume-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
              &lt;span class="c1"&gt;# Port Configuration&lt;/span&gt;
              &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;array&lt;/span&gt;
                &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
                  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                    &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
                      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;int64&lt;/span&gt;
                    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
                      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;int64&lt;/span&gt;
              &lt;span class="c1"&gt;# Environment variables&lt;/span&gt;
              &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that even the Specification is also a resource in Kubernetes of kind &lt;code&gt;CustomResourceDefinition&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can now store that code in the file located in &lt;code&gt;app-operator/config/crd.yml&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Go Lang CRD Definition
&lt;/h4&gt;

&lt;p&gt;We can now define our CRD in Go code. When defininga CRD in Go. We need to do the following&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define the CRD Specification&lt;/li&gt;
&lt;li&gt;Define a &lt;em&gt;deepCopy&lt;/em&gt; function that defines how kubernetes copies the CRD object into another object&lt;/li&gt;
&lt;li&gt;Setup code for Registering the CRD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use Go Structs to define the CRD. Probably due to how it is easy to define JSON-like data structures usung structs. Create a file in &lt;code&gt;app-operator/api/v1/application.go&lt;/code&gt; and save the following code in it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;metav1&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/apis/meta/v1"&lt;/span&gt;

&lt;span class="c"&gt;// This defines an instance of multiple Application resources&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ApplicationList&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeMeta&lt;/span&gt; &lt;span class="s"&gt;`json:",inline"`&lt;/span&gt;
  &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListMeta&lt;/span&gt; &lt;span class="s"&gt;`json:"metadata,omitempty"`&lt;/span&gt;

  &lt;span class="n"&gt;Items&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt; &lt;span class="s"&gt;`json:"items"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// This defines our CRD&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeMeta&lt;/span&gt;   &lt;span class="s"&gt;`json:",inline"`&lt;/span&gt;
  &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectMeta&lt;/span&gt; &lt;span class="s"&gt;`json:"metadata,omitempty"`&lt;/span&gt;

  &lt;span class="n"&gt;Spec&lt;/span&gt; &lt;span class="n"&gt;ApplicationSpec&lt;/span&gt; &lt;span class="s"&gt;`json:"spec"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ApplicationSpec&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Image&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;               &lt;span class="s"&gt;`json:"image"`&lt;/span&gt;
  &lt;span class="n"&gt;Volumes&lt;/span&gt;   &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;ApplicationVolume&lt;/span&gt;  &lt;span class="s"&gt;`json:"volumes"`&lt;/span&gt;
  &lt;span class="n"&gt;Ports&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;ApplicationPortMap&lt;/span&gt; &lt;span class="s"&gt;`json:"ports"`&lt;/span&gt;
  &lt;span class="n"&gt;EnvFrom&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt;               &lt;span class="s"&gt;`json:"envFrom"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ApplicationVolume&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;VolumeName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"volume-name"`&lt;/span&gt;
  &lt;span class="n"&gt;Path&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"path"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ApplicationPortMap&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Name&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"name"`&lt;/span&gt;
  &lt;span class="n"&gt;Internal&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="s"&gt;`json:"internal"`&lt;/span&gt;
  &lt;span class="n"&gt;External&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="s"&gt;`json:"external"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next lets write the code that defines the Deep Copy functions. In you project create a file in &lt;code&gt;app-operator/api/v1/deepcopy.go&lt;/code&gt;. And add the following code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime"&lt;/span&gt;

&lt;span class="c"&gt;// DeepCopyInto copies all properties of this object into another object of the&lt;/span&gt;
&lt;span class="c"&gt;// same type that is provided as a pointer.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;DeepCopyInto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeMeta&lt;/span&gt;
  &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectMeta&lt;/span&gt;
  &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ApplicationSpec&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Volumes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Volumes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Ports&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EnvFrom&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnvFrom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// DeepCopyObject returns a generically typed copy of an object&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;DeepCopyObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeepCopyInto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// DeepCopyObject returns a generically typed copy of an object&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ApplicationList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;DeepCopyObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ApplicationList&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeMeta&lt;/span&gt;
  &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListMeta&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeepCopyInto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly lets write the code that defines how our CRD is registed. In you project create a file in &lt;code&gt;app-operator/api/v1/register.go&lt;/code&gt;. And add the following code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;metav1&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/apis/meta/v1"&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime"&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime/schema"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Define the API group name for the custom resource&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;GroupName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"operator.com"&lt;/span&gt;
&lt;span class="c"&gt;// Define the API group version for the custom resource&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;GroupVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v1"&lt;/span&gt;

&lt;span class="c"&gt;// Create a GroupVersion object that combines the group and version&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;SchemeGroupVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GroupVersion&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GroupName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GroupVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c"&gt;// SchemeBuilder is a runtime.SchemeBuilder used to add types to the scheme&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;SchemeBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSchemeBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addKnownTypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// Initializes the SchemeBuilder with the addKnownTypes function&lt;/span&gt;
  &lt;span class="n"&gt;AddToScheme&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SchemeBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddToScheme&lt;/span&gt;  &lt;span class="c"&gt;// Provides a shorthand for adding types to the scheme&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c"&gt;// addKnownTypes registers the custom resource types with the runtime.Scheme&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;addKnownTypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// Register the custom resources Application and ApplicationList with the scheme&lt;/span&gt;
  &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddKnownTypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SchemeGroupVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ApplicationList&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;// Add the group version to the scheme for metav1 objects&lt;/span&gt;
  &lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddToGroupVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SchemeGroupVersion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="c"&gt;// Return nil to indicate success&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now move on to the next section of Implementing the Controller that will transform our CRD state into the desired objects on the kubernetes cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing the Controller
&lt;/h3&gt;

&lt;p&gt;Now that we have our CRD definitions implemented, lets move on to creating our controller that will watch the CRD state and transform it into desired Kubernetes onjects which will be Deployements and Services. To accomplish this we shall visits a few of the concepts we metioned earlier about the Controllers. We shall create the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Manager that will be the entry point of our Controller&lt;/li&gt;
&lt;li&gt;The Reconcile function that reconciles the CRD state into desired state on the Cluster&lt;/li&gt;
&lt;li&gt;Utility functions that carry out the tasks our operator intends to accomplish&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;code&gt;app-operator/cmd/controller/reconciler.go&lt;/code&gt;, paste this code. Dont wory we shall look at each block in detail and what it accomplishes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"context"&lt;/span&gt;
  &lt;span class="s"&gt;"errors"&lt;/span&gt;
  &lt;span class="s"&gt;"fmt"&lt;/span&gt;
  &lt;span class="s"&gt;"os"&lt;/span&gt;
  &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;

  &lt;span class="n"&gt;cranev1&lt;/span&gt; &lt;span class="s"&gt;"github.com/jim-junior/crane-operator/api/v1"&lt;/span&gt;
  &lt;span class="n"&gt;craneKubeUtils&lt;/span&gt; &lt;span class="s"&gt;"github.com/jim-junior/crane-operator/kube"&lt;/span&gt;

  &lt;span class="n"&gt;k8serrors&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/api/errors"&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime"&lt;/span&gt;
  &lt;span class="n"&gt;utilruntime&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/util/runtime"&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/client-go/kubernetes"&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/client-go/rest"&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/client-go/tools/clientcmd"&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/client-go/util/homedir"&lt;/span&gt;
  &lt;span class="n"&gt;ctrl&lt;/span&gt; &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime"&lt;/span&gt;
  &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/client"&lt;/span&gt;
  &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/log"&lt;/span&gt;
  &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/log/zap"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Global variables for the Kubernetes scheme and logger&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;scheme&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;setupLog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"setup"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Initialize the scheme by registering the cranev1 API group&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;utilruntime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cranev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddToScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Reconciler structure to handle reconciliation logic&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Reconciler&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;            &lt;span class="c"&gt;// Controller-runtime client&lt;/span&gt;
  &lt;span class="n"&gt;scheme&lt;/span&gt;     &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt; &lt;span class="c"&gt;// Scheme for managing API types&lt;/span&gt;
  &lt;span class="n"&gt;kubeClient&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;kubernetes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clientset&lt;/span&gt; &lt;span class="c"&gt;// Kubernetes clientset for direct API calls&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Reconcile function handles the main logic for the controller&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Reconciler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Create contextual logger&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"reconciling application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;// Fetch the Application resource by name and namespace&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="n"&gt;cranev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;
  &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// If resource is not found, attempt to clean up associated resources&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k8serrors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;craneKubeUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kubeClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"couldn't delete resources: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Create or update the Kubernetes deployment for the Application resource&lt;/span&gt;
  &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;craneKubeUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kubeClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"couldn't create or update deployment: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="c"&gt;// Reconcile completed successfully&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// RunController initializes and starts the Kubernetes controller&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RunController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt;    &lt;span class="kt"&gt;error&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;// Determine the kubeconfig file path (used for local development)&lt;/span&gt;
  &lt;span class="n"&gt;kubeconfigFilePath&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;homedir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HomeDir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;".kube"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kubeconfigFilePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNotExist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// If kubeconfig does not exist, try to use in-cluster configuration&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InClusterConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c"&gt;// Exit if no valid configuration is found&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Load configuration from kubeconfig file&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientcmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildConfigFromFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kubeconfigFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Create a Kubernetes clientset&lt;/span&gt;
  &lt;span class="n"&gt;clientset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kubernetes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewForConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Set up the logger for the controller&lt;/span&gt;
  &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="c"&gt;// Create a new manager for the controller&lt;/span&gt;
  &lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setupLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unable to start manager"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Create and register the reconciler with the manager&lt;/span&gt;
  &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewControllerManagedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;For&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cranev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="c"&gt;// Specify the resource type the controller manages&lt;/span&gt;
    &lt;span class="n"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Reconciler&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c"&gt;// Use manager's client&lt;/span&gt;
      &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetScheme&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  &lt;span class="c"&gt;// Use manager's scheme&lt;/span&gt;
      &lt;span class="n"&gt;kubeClient&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clientset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c"&gt;// Use clientset for direct API calls&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setupLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unable to create controller"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Start the manager and handle graceful shutdown&lt;/span&gt;
  &lt;span class="n"&gt;setupLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"starting manager"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetupSignalHandler&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setupLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"error running manager"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Lets explain the Controller Code
&lt;/h4&gt;

&lt;p&gt;Lets take a deep dive into the code above so we can have a good understanding of whats going on in that file.&lt;/p&gt;

&lt;p&gt;At the top of the file we are importing the required libraries that we shall use. Read the comments in this code snippet to understand what each import does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"context"&lt;/span&gt; &lt;span class="c"&gt;// Provides functionality for managing and passing context, especially useful in request-scoped operations.&lt;/span&gt;
  &lt;span class="s"&gt;"errors"&lt;/span&gt;  &lt;span class="c"&gt;// Standard Go package for creating and handling errors.&lt;/span&gt;
  &lt;span class="s"&gt;"fmt"&lt;/span&gt;     &lt;span class="c"&gt;// Provides formatted I/O with functions similar to C's printf and scanf.&lt;/span&gt;
  &lt;span class="s"&gt;"os"&lt;/span&gt;      &lt;span class="c"&gt;// Handles OS-level functionalities such as reading environment variables and file system operations.&lt;/span&gt;
  &lt;span class="s"&gt;"path/filepath"&lt;/span&gt; &lt;span class="c"&gt;// Helps in manipulating and building file paths in a cross-platform way.&lt;/span&gt;

  &lt;span class="n"&gt;cranev1&lt;/span&gt; &lt;span class="s"&gt;"github.com/jim-junior/crane-operator/api/v1"&lt;/span&gt; &lt;span class="c"&gt;// Imports the custom CRD definitions (e.g., Application) for this operator.&lt;/span&gt;
  &lt;span class="n"&gt;craneKubeUtils&lt;/span&gt; &lt;span class="s"&gt;"github.com/jim-junior/crane-operator/kube"&lt;/span&gt; &lt;span class="c"&gt;// Imports helper utilities for interacting with Kubernetes resources.&lt;/span&gt;

  &lt;span class="n"&gt;k8serrors&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/api/errors"&lt;/span&gt; &lt;span class="c"&gt;// Provides utilities for working with Kubernetes API errors.&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime"&lt;/span&gt; &lt;span class="c"&gt;// Handles runtime types and schemes for Kubernetes objects.&lt;/span&gt;
  &lt;span class="n"&gt;utilruntime&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/util/runtime"&lt;/span&gt; &lt;span class="c"&gt;// Contains utility functions for runtime error handling and recovery.&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/client-go/kubernetes"&lt;/span&gt; &lt;span class="c"&gt;// Kubernetes client-go library for interacting with the Kubernetes API server.&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/client-go/rest"&lt;/span&gt; &lt;span class="c"&gt;// Provides tools for working with REST configurations, especially for in-cluster access.&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/client-go/tools/clientcmd"&lt;/span&gt; &lt;span class="c"&gt;// Handles loading and parsing kubeconfig files for out-of-cluster Kubernetes access.&lt;/span&gt;
  &lt;span class="s"&gt;"k8s.io/client-go/util/homedir"&lt;/span&gt; &lt;span class="c"&gt;// Utility package to get the user's home directory path.&lt;/span&gt;

  &lt;span class="n"&gt;ctrl&lt;/span&gt; &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime"&lt;/span&gt; &lt;span class="c"&gt;// Main package for building controllers using the Kubernetes Controller Runtime.&lt;/span&gt;
  &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/client"&lt;/span&gt; &lt;span class="c"&gt;// Provides a dynamic client for interacting with Kubernetes objects.&lt;/span&gt;
  &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/log"&lt;/span&gt; &lt;span class="c"&gt;// Utilities for logging within the Controller Runtime framework.&lt;/span&gt;
  &lt;span class="s"&gt;"sigs.k8s.io/controller-runtime/pkg/log/zap"&lt;/span&gt; &lt;span class="c"&gt;// Provides a Zap-based logger for the Controller Runtime.&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have an &lt;code&gt;init&lt;/code&gt; function that initialize the Kubernetes scheme by registering the API group we defined for our CRD.&lt;/p&gt;

&lt;p&gt;Next lets look ar the &lt;code&gt;RunController&lt;/code&gt; function at the bottom of the file. First we need to get access to a Kuberentes client or clientset that will allow use to interact with the Kubernetes API Server when carrying out CRUD operations on our cluster. That what these first 27 lines are trying to accomplish. We shall call the &lt;code&gt;RunController&lt;/code&gt; function in the &lt;code&gt;main.go&lt;/code&gt; file of our program as it will be the entry point of out Kubernetes Operator&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;kubeconfigFilePath&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;homedir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HomeDir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;".kube"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kubeconfigFilePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNotExist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// If kubeconfig does not exist, try to use in-cluster configuration&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InClusterConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c"&gt;// Exit if no valid configuration is found&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Load configuration from kubeconfig file&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientcmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildConfigFromFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kubeconfigFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Create a Kubernetes clientset&lt;/span&gt;
  &lt;span class="n"&gt;clientset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kubernetes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewForConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then setup logging wth zap. This will help us in debugging&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then setup our Manager.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a new manager for the controller&lt;/span&gt;
  &lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setupLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unable to start manager"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Create and register the reconciler with the manager&lt;/span&gt;
  &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewControllerManagedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;For&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cranev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="c"&gt;// Specify the resource type the controller manages&lt;/span&gt;
    &lt;span class="n"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Reconciler&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c"&gt;// Use manager's client&lt;/span&gt;
      &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetScheme&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  &lt;span class="c"&gt;// Use manager's scheme&lt;/span&gt;
      &lt;span class="n"&gt;kubeClient&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clientset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c"&gt;// Use clientset for direct API calls&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setupLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unable to create controller"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Start the manager and handle graceful shutdown&lt;/span&gt;
  &lt;span class="n"&gt;setupLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"starting manager"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetupSignalHandler&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setupLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"error running manager"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code initializes and starts the core components of the Kubernetes controller. First, a manager (&lt;code&gt;mgr&lt;/code&gt;) is created using the &lt;code&gt;ctrl.NewManager&lt;/code&gt; function, which serves as the runtime environment for the controller, handling shared resources, clients, and the scheme that defines the resource types the manager can work with. If the manager cannot start due to configuration issues, an error is logged, and the program exits. Next, a reconciler is created and registered with the manager. The reconciler defines the logic for ensuring the desired state of the custom resource (&lt;code&gt;Application&lt;/code&gt;) matches the actual state in the cluster. This is done using &lt;code&gt;ctrl.NewControllerManagedBy&lt;/code&gt;, which specifies the resource type the controller will manage and configures the reconciler with the manager's client, scheme, and a Kubernetes clientset for making direct API calls. If the controller cannot be created or registered, the program exits with an error. Finally, the manager is started using &lt;code&gt;mgr.Start&lt;/code&gt;, which begins watching the specified resource and handling reconciliation requests. A signal handler is set up to ensure graceful shutdowns. If the manager fails to start, an error is logged, and the program exits. This setup combines the manager and reconciler to enable the operator to monitor and maintain the desired state of custom resources in the Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;Next lets define our Reconciler and &lt;code&gt;Reconcile&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;We define the Reconciler struct. This is the struct that we used in the &lt;code&gt;ctrl.NewControllerManagedBy&lt;/code&gt; when we initialized the controller. The struct should have a method called &lt;code&gt;Reconcile&lt;/code&gt; that handles the main logic for the controller, i.e it reconciles the desired state into actual Kubernetes Objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Reconciler&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;
  &lt;span class="n"&gt;scheme&lt;/span&gt;     &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheme&lt;/span&gt;
  &lt;span class="n"&gt;kubeClient&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;kubernetes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clientset&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Reconciler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Create contextual logger&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"reconciling application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;// Fetch the Application resource by name and namespace&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="n"&gt;cranev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;
  &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespacedName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// If resource is not found, attempt to clean up associated resources&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k8serrors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;craneKubeUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kubeClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"couldn't delete resources: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// Create or update the Kubernetes deployment for the Application resource&lt;/span&gt;
  &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;craneKubeUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kubeClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"couldn't create or update deployment: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="c"&gt;// Reconcile completed successfully&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I will not include utility functions imported as &lt;code&gt;craneKubeUtils&lt;/code&gt; because they are not really necessary for this article but they are basically functions that create &lt;em&gt;Deployements&lt;/em&gt; and &lt;em&gt;Services&lt;/em&gt; from the CRD Spec. However, in the code hosted on the GitHub repository, you can find them in this file. &lt;a href="https://github.com/jim-junior/crane-operator/blob/main/kube/application.go" rel="noopener noreferrer"&gt;https://github.com/jim-junior/crane-operator/blob/main/kube/application.go&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next in your &lt;code&gt;main.go&lt;/code&gt; file import the &lt;code&gt;RunController&lt;/code&gt; function from the &lt;code&gt;controller&lt;/code&gt; package and call it in you &lt;code&gt;main()&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/jim-junior/crane-operator/cmd/controller"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing the Controller
&lt;/h3&gt;

&lt;p&gt;Now that we are done writing the code for our controller, we can test it. First you will have to have you kubernetes cluster running and you config setup. Make sure the host machine that you are running it on is already connected to the kubernetes cluster. you can verify this by running any &lt;code&gt;kubectl&lt;/code&gt; command such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodes
&lt;span class="c"&gt;# If it setup it might return something like this&lt;/span&gt;
&lt;span class="c"&gt;# NAME       STATUS   ROLES           AGE    VERSION&lt;/span&gt;
&lt;span class="c"&gt;# minikube   Ready    control-plane   228d   v1.27.4&lt;/span&gt;

&lt;span class="c"&gt;# If its not setup it might return an error like: &lt;/span&gt;
&lt;span class="c"&gt;# E1222 11:35:37.597805   25720 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connectex: No connection could be made because the target machine actively refused it.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all is well you can move on to test our controller by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run main.go 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected log output is something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"level":"info","ts":"2024-12-22T11:08:23+03:00","logger":"setup","msg":"starting manager"}
{"level":"info","ts":"2024-12-22T11:08:23+03:00","logger":"controller-runtime.metrics","msg":"Starting metrics server"}
{"level":"info","ts":"2024-12-22T11:08:23+03:00","msg":"Starting EventSource","controller":"application","controllerGroup":"cloud.cranom.tech","controllerKind":"Application","source":"kind source: *v1.Application"}
{"level":"info","ts":"2024-12-22T11:08:23+03:00","msg":"Starting Controller","controller":"application","controllerGroup":"cloud.cranom.tech","controllerKind":"Application"}
{"level":"info","ts":"2024-12-22T11:08:23+03:00","logger":"controller-runtime.metrics","msg":"Serving metrics server","bindAddress":":8080","secure":false}
{"level":"info","ts":"2024-12-22T11:08:23+03:00","msg":"Starting workers","controller":"application","controllerGroup":"cloud.cranom.tech","controllerKind":"Application","worker count":1}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your getting an error or having a hard time running the controller for some reason, you can open an issue on the Github Repository where the source code is hosted or you can leave it in the comment section if your reading this article on Dev.to or Medium.&lt;/p&gt;

&lt;p&gt;If all is good. We can now try applying an example CRD and test if our operator is carrying out its expected functionality.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;yaml/examples&lt;/code&gt; directory of the github repository i have placed there a cofigurations or our custom &lt;code&gt;Application&lt;/code&gt; resource. These configuration are for deploying a Wordpress instance with a Mysql database. there are three files. &lt;code&gt;mysql.yaml&lt;/code&gt;, &lt;code&gt;wp-secrets.yml&lt;/code&gt; and &lt;code&gt;wordpress.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can apply them by running tthe &lt;code&gt;kubectl apply&lt;/code&gt; commands in this order.&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="c"&gt;# Secrets for the Environment Variables&lt;/span&gt;
kubectl apply yaml/examples/wp-secrets.yml
&lt;span class="c"&gt;# Mysql instance&lt;/span&gt;
kubectl apply yaml/examples/mysql.yml
&lt;span class="c"&gt;# Wordpress instance&lt;/span&gt;
kubectl apply yaml/examples/wordpress.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also copy the code from these files on Github&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jim-junior/crane-operator/blob/main/yaml/examples/mysql.yaml" rel="noopener noreferrer"&gt;https://github.com/jim-junior/crane-operator/blob/main/yaml/examples/mysql.yaml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jim-junior/crane-operator/blob/main/yaml/examples/wordpress.yml" rel="noopener noreferrer"&gt;https://github.com/jim-junior/crane-operator/blob/main/yaml/examples/wordpress.yml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jim-junior/crane-operator/blob/main/yaml/examples/wp-secrets.yml" rel="noopener noreferrer"&gt;https://github.com/jim-junior/crane-operator/blob/main/yaml/examples/wp-secrets.yml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The configurations in those files are for a Wordpress application that will listen on the node port of &lt;code&gt;30080&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying the Operator
&lt;/h3&gt;

&lt;p&gt;Since we now know the kubernetes operator works, Lets move on to deploying it. To deploy it, we shall have to carry out the following steps.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate a docker image for the operator&lt;/li&gt;
&lt;li&gt;Publish it to the Docker registry&lt;/li&gt;
&lt;li&gt;We can they deploy it on our cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We shall move on to generating a container image for the Kubernetes Operator. We shall use the &lt;a href=""&gt;golang&lt;/a&gt; image as our base image for the container since this is a golang project. Copy this code and add it to your &lt;code&gt;DockerFile&lt;/code&gt; in the root of you project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Building the binary of the App&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.22.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="c"&gt;# `boilerplate` should be replaced with your project name&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /go/src/boilerplate&lt;/span&gt;

&lt;span class="c"&gt;# Copy all the Code and stuff to compile everything&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Downloads all the dependencies in advance (could be left out, but it's more clear this way)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download

&lt;span class="c"&gt;# Builds the application as a staticly linked one, to allow it to run on alpine&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 go build &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-installsuffix&lt;/span&gt; cgo &lt;span class="nt"&gt;-o&lt;/span&gt; app .


&lt;span class="c"&gt;# Moving the binary to the 'final Image' to make it smaller&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;release&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;


&lt;span class="c"&gt;# `boilerplate` should be replaced here as well&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /go/src/boilerplate/app .&lt;/span&gt;

&lt;span class="c"&gt;# Add packages&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk &lt;span class="nt"&gt;-U&lt;/span&gt; upgrade &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; dumb-init ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod&lt;/span&gt; +x /app/app


&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; [ "/app/app" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then build the container using docker and then publish it to a container registry of your choice.&lt;/p&gt;

&lt;p&gt;Then next is to deploy our cluster. We shall create a Kubernetes deployment for our Operator. Below i have a simple Kubernetes &lt;em&gt;Deployment&lt;/em&gt; object for the operator.&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crane-operator&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crane-operator&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crane-operator&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;controller&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jimjuniorb/crane-operator:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Congrats!!!&lt;/strong&gt; you now have a running Kubernetes operator on your cluster. You can now adjust it in what ever way you would like.&lt;/p&gt;

&lt;p&gt;I would recommend looking into frameworks like Operator SDK or Kube builder if you want to build more complex Operators. I have also included a Github Action workflow file for deploying the Operator using GitHub actions each time a new Release tag is created.&lt;/p&gt;

&lt;p&gt;Well thats it for Today. Thanks for following through till the End of this article.  You can check our my other articles on my &lt;a href="https://jim-junior.github.io/#/blog" rel="noopener noreferrer"&gt;Blog&lt;/a&gt;. Below are a few references i used that you might find helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Brandon Philips, (November 3, 2016). Introducing Operators: Putting Operational Knowledge into Software. Internet Archive Wayback Machine. &lt;a href="https://web.archive.org/web/20170129131616/https://coreos.com/blog/introducing-operators.html" rel="noopener noreferrer"&gt;https://web.archive.org/web/20170129131616/https://coreos.com/blog/introducing-operators.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CNCF TAG App-Delivery Operator Working Group, CNCF Operator White Paper - Final Version. Github. &lt;a href="https://github.com/cncf/tag-app-delivery/blob/163962c4b1cd70d085107fc579e3e04c2e14d59c/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md" rel="noopener noreferrer"&gt;https://github.com/cncf/tag-app-delivery/blob/163962c4b1cd70d085107fc579e3e04c2e14d59c/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes Documentation, Custom Resources. &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes Documentation, Controllers. &lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Youtube, Kubernetes API Versioning: A Deep dive. &lt;a href="https://www.youtube.com/live/-jtGM6WnF1Q?si=-6tfrlwyTf-NSizL" rel="noopener noreferrer"&gt;https://www.youtube.com/live/-jtGM6WnF1Q?si=-6tfrlwyTf-NSizL&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>go</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to create a responsive custom video player in React</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Sat, 04 May 2024 13:10:35 +0000</pubDate>
      <link>https://forem.com/jimjunior/how-to-create-a-responsive-custom-video-player-in-react-4m7f</link>
      <guid>https://forem.com/jimjunior/how-to-create-a-responsive-custom-video-player-in-react-4m7f</guid>
      <description>&lt;p&gt;Video players are essential for applications that showcase video content. While the default HTML5 player offers basic functionalities like play, pause, and volume control, its appearance can vary significantly across browsers and devices.  This inconsistency can be a drawback for a polished user experience.&lt;/p&gt;

&lt;p&gt;In this article, we'll guide you through creating a responsive custom video player using a library I built called &lt;code&gt;reactjs-media&lt;/code&gt;. This approach lets you avoid the limitations of the default HTML5 player and the complexity of building a video player from scratch. The reactjs-media library provides more react approach to building your custom player with a wide range of features and customization options.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;UPDATE: The reactjs-media documentation was shifted to open.cranom.tech&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Before we dive into building our custom video player, I assume your already have a React project set up. If not, you can create a new React project using Create React App or Vite or any of your preferred React setup.&lt;/p&gt;

&lt;p&gt;We shall then go ahead and install the reactjs-media library using npm or yarn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;reactjs-media
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add reactjs-media
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a custom video player
&lt;/h2&gt;

&lt;p&gt;To create a Video player we shall start with the default player provided by the library(Which is Awesome too).&lt;/p&gt;

&lt;p&gt;All you have to do is import the &lt;code&gt;Video&lt;/code&gt; component from &lt;code&gt;reactjs-media&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Video&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reactjs-media&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Video&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/video.mkv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;poster&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://hips.hearstapps.com/hmg-prod/images/ripley-pa-108-011822-01629-r-661067043d66f.jpg?resize=980:*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;And Voila!&lt;/strong&gt; You have a video player in your application.&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%2Fbsxsuwlbusxeve0zf1em.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%2Fbsxsuwlbusxeve0zf1em.png" alt="reactjs-media video player" width="800" height="458"&gt;&lt;/a&gt;&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%2Fl9zb7fepyracfsxv92qf.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%2Fl9zb7fepyracfsxv92qf.png" alt="reactjs-media video poster" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the props?
&lt;/h2&gt;

&lt;p&gt;The Video Component accepts a number props that are defined under the &lt;code&gt;VideoProps&lt;/code&gt; interface(If you use TypeScript in your Projects). These can be both attributes of the player or event handlers.&lt;/p&gt;

&lt;p&gt;We shall start with the basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src&lt;/code&gt;: The source of the video file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;controls&lt;/code&gt;: A boolean value to show or hide the video controls. It defaults to &lt;code&gt;false&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;height&lt;/code&gt;: The height of the video player.(This is added as &lt;code&gt;maxHeight&lt;/code&gt; in the styles)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;width&lt;/code&gt;: The width of the video player.(This is added as &lt;code&gt;maxWidth&lt;/code&gt; in the styles)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;poster&lt;/code&gt;: The poster image to be displayed before the video starts playing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contextMenu&lt;/code&gt;: A boolean value to show or hide the context menu. It defaults to &lt;code&gt;false&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Handling Events
&lt;/h2&gt;

&lt;p&gt;The Video Component also provides a number of event handlers that you can use to listen to events on the video player.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;onPlay&lt;/code&gt;: This event is fired when the video starts playing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onPause&lt;/code&gt;: This event is fired when the video is paused.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onEnded&lt;/code&gt;: This event is fired when the video has ended.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onTimeUpdate&lt;/code&gt;: This event is fired when the video is playing and the time is updated.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onVolumeChange&lt;/code&gt;: This event is fired when the volume of the video is changed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onSeeking&lt;/code&gt;: This event is fired when the video is seeking.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onSeeked&lt;/code&gt;: This event is fired when the video has finished seeking.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onCanPlay&lt;/code&gt;: This event is fired when the video can be played.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Video&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/video.mkv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;poster&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://hips.hearstapps.com/hmg-prod/images/ripley-pa-108-011822-01629-r-661067043d66f.jpg?resize=980:*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onPlay&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Video is playing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onPause&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Video is paused&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onEnded&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Video has ended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onTimeUpdate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Time is updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you want to build a player with your own Completely Custom UI. You can check out the &lt;a href="https://open.cranom.tech/reactjs-media/building-custom-player" rel="noopener noreferrer"&gt;Building a custom player&lt;/a&gt; guide in the &lt;code&gt;reactjs-media&lt;/code&gt; documentation.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;In this article, we have learned how to create a responsive custom video player in React using the &lt;code&gt;reactjs-media&lt;/code&gt; library. This library provides a set of components and a simple API to create a customizable media player for your application. You can explore more features and customization options by checking out the &lt;a href="https://open.cranom.tech/reactjs-media/intro" rel="noopener noreferrer"&gt;&lt;code&gt;reactjs-media&lt;/code&gt; documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>react</category>
      <category>video</category>
    </item>
    <item>
      <title>Creating a URL Shortener service in Python Django</title>
      <dc:creator>Beingana Jim Junior</dc:creator>
      <pubDate>Mon, 08 Aug 2022 13:57:03 +0000</pubDate>
      <link>https://forem.com/jimjunior/creating-a-url-shortener-service-in-python-django-1120</link>
      <guid>https://forem.com/jimjunior/creating-a-url-shortener-service-in-python-django-1120</guid>
      <description>&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%2Fzqnl7ongem2tduxe49s7.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%2Fzqnl7ongem2tduxe49s7.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;URL shorteners have become a popular service on the web. Companies like &lt;a href="https://bitly.com/" rel="noopener noreferrer"&gt;bitly&lt;/a&gt; are making great fortunes from them. But sometimes when you want a custom URL you get to pay for the service. So in this tutorial I am going to show you how to build a URL shortener service in Django.&lt;/p&gt;

&lt;p&gt;In this tutorial I expect you to be familiar with templates and forms because i wont cover them but instead i will just show you how to pragmatically build create the service in python code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;To setup the project, we shall need only one package. django-urlshortner. So lets begin by installing the package using pip. This asumes you have already setup a django project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install django-urlshortner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you should add the urlshortner app to your INSTALLED_APPS in settings.py&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ....
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urlshortner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you migrate the models to your database&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly add the routes to your URLConf in your urls.py of your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;url_patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urlshortner.urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you are good to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;The library provides a list of utils to create shortened urls.&lt;/p&gt;

&lt;p&gt;To create a short version of a url use the shorten_url function from urlshortner.utils module&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# python3 manage.py shell
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urlshortner.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shorten_url&lt;/span&gt;

&lt;span class="n"&gt;url_route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;shorten_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/jim-junior/django-urlshortner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_permanent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# &amp;gt;&amp;gt;&amp;gt; 0ee3f0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now navigate to you the route that you assigned to urlshortner.urls in your URLConf add the returned value att the end of the url. In this case it would be &lt;a href="http://localhost:8000/r/0ee3f0/" rel="noopener noreferrer"&gt;http://localhost:8000/r/0ee3f0/&lt;/a&gt; and this would redirect you to the right URL&lt;/p&gt;

&lt;p&gt;Sometimes you want to create a custom URL. For example you want to create a short link for a blog about your new product and you want a url that is easy to remember. You can add this easily by adding the value argument to the shorten_url function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urlshortner.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shorten_url&lt;/span&gt;

&lt;span class="n"&gt;url_route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;shorten_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://myblog.com/blog/2022/10/10/..../my-new-product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NewProduct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;is_permanent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now navigate to &lt;a href="https://localhost/r/NewProduct" rel="noopener noreferrer"&gt;https://localhost/r/NewProduct&lt;/a&gt; and It will redirect you&lt;/p&gt;

&lt;p&gt;Now that you know how to use the package i think you can intergrate it with you project.&lt;/p&gt;

&lt;p&gt;Hope this article was helpful. You can git the project a star on &lt;a href="https://github.com/jim-junior/django-urlshortner" rel="noopener noreferrer"&gt;Github&lt;/a&gt; or if you have any idea to add on you can contribute to its &lt;a href="https://github.com/jim-junior/django-urlshortner" rel="noopener noreferrer"&gt;repository&lt;/a&gt;. And bythaway I am the Author of this library.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>bitly</category>
      <category>python</category>
      <category>urlshorteners</category>
    </item>
  </channel>
</rss>
