<?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: Hugo Bollon</title>
    <description>The latest articles on Forem by Hugo Bollon (@hbollon).</description>
    <link>https://forem.com/hbollon</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%2F471841%2F5b30e145-937e-4e18-ab23-66c2cc606559.jpg</url>
      <title>Forem: Hugo Bollon</title>
      <link>https://forem.com/hbollon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hbollon"/>
    <language>en</language>
    <item>
      <title>I Rebuilt My Developer Portfolio with Nuxt 4 + Strapi and Made It Open Source</title>
      <dc:creator>Hugo Bollon</dc:creator>
      <pubDate>Sat, 21 Mar 2026 11:46:45 +0000</pubDate>
      <link>https://forem.com/hbollon/i-rebuilt-my-developer-portfolio-with-nuxt-4-strapi-and-made-it-open-source-cnh</link>
      <guid>https://forem.com/hbollon/i-rebuilt-my-developer-portfolio-with-nuxt-4-strapi-and-made-it-open-source-cnh</guid>
      <description>&lt;p&gt;If you've been putting off rebuilding your portfolio because it feels like a weekend project that always gets pushed to the following month, I understand. Mine sat on Vue 2 and CosmicJS for years. It still worked, but it carried the kind of technical debt that makes you hesitate every time someone asks for the repository.&lt;/p&gt;

&lt;p&gt;This is the story of how I finally rebuilt it from scratch, why I made certain architectural decisions, and why I ended up open-sourcing it as a reusable Nuxt 4 + Strapi portfolio starter.&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%2Fv91jngwojpwbw8uvx3e1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv91jngwojpwbw8uvx3e1.gif" alt="Portfolio preview" width="760" height="429"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Most Developer Portfolios
&lt;/h2&gt;

&lt;p&gt;Here's a pattern I've noticed: developers spend a lot of energy on the initial build, then abandon the thing entirely. The portfolio accumulates outdated tech, unmaintained dependencies, and content that no longer reflects who they are.&lt;/p&gt;

&lt;p&gt;The reasons are usually the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No CMS&lt;/strong&gt; — updating content requires a code change, a commit, a deploy. That friction kills momentum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outdated stack&lt;/strong&gt; — Vue 2, Create React App, Gatsby v2, Jekyll. Not inherently bad, but try explaining that to a recruiter who opens DevTools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor SEO baseline&lt;/strong&gt; — client-side rendered apps with no server-rendered meta tags, missing OG images, no sitemap. Google does eventually figure it out, but you're fighting it rather than working with it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard to reuse&lt;/strong&gt; — most portfolios are so tightly coupled to the original author's content that forking one means untangling dozens of hardcoded strings and personal data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My old portfolio (&lt;a href="https://github.com/hbollon/portfolio-vuejs" rel="noopener noreferrer"&gt;github.com/hbollon/portfolio-vuejs&lt;/a&gt;) was a good project at the time. Vue 2, CosmicJS as the CMS. It worked. But every time I wanted to update my bio or add a new project, I found myself doing more than I wanted to do just to push a change. And the codebase had grown organically in ways that made it uncomfortable to share.&lt;/p&gt;

&lt;p&gt;I wanted something different: a portfolio I'd actually enjoy maintaining, built on a stack I use professionally, with real content management, and structured so someone else could fork it and use it without major refactoring.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: An Open-Source Nuxt 4 + Strapi Portfolio Starter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub: &lt;a href="https://github.com/hbollon/portfolio-nuxt" rel="noopener noreferrer"&gt;github.com/hbollon/portfolio-nuxt&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Live demo: &lt;a href="https://hugobollon.dev" rel="noopener noreferrer"&gt;hugobollon.dev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project is a statically generated developer portfolio built with &lt;strong&gt;Nuxt 4&lt;/strong&gt; and &lt;strong&gt;Strapi v5&lt;/strong&gt;, fully open source under the MIT license. It's designed to be a working starting point, not just an inspiration screenshot. You fork it, point it at your own Strapi instance, fill in your content, and deploy.&lt;/p&gt;


&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nuxt 4 with SSG&lt;/strong&gt; — fully pre-rendered HTML, deployed to S3 + CloudFront. No server runtime in production. Excellent Lighthouse scores by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strapi v5 as headless CMS&lt;/strong&gt; — content is fetched at build time only. Strapi is never exposed to end users. You get a full admin interface to manage projects, experience, education, and bio without touching code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EN/FR internationalization&lt;/strong&gt; — built-in support for two locales with &lt;code&gt;@nuxtjs/i18n&lt;/code&gt;. UI strings live in local JSON files; editorial content (bio, projects, experience descriptions) is translated inside Strapi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Space/cosmos dark theme&lt;/strong&gt; — custom design system with a consistent token palette (deep blacks, nebula purples, electric blues). Glassmorphism cards, particle backgrounds via tsparticles, scroll-triggered animations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full SEO setup&lt;/strong&gt; — automatic sitemap, hreflang for each locale, Open Graph and Twitter Card tags, JSON-LD structured data, canonical URLs. Everything handled by a &lt;code&gt;useSeo()&lt;/code&gt; composable, configurable per-page from Strapi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Umami analytics&lt;/strong&gt; — privacy-friendly analytics as an alternative to Google Analytics. GDPR-compliant by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD with GitHub Actions + AWS OIDC&lt;/strong&gt; — no static AWS credentials. The deployment workflow assumes an IAM role via OIDC token, syncs the build output to S3, and invalidates CloudFront.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback content mode&lt;/strong&gt; — if you want to run it locally without a Strapi instance, the build falls back to static content files. Useful for quickly evaluating the template.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Technical Highlights
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Strapi consumed at build time only
&lt;/h3&gt;

&lt;p&gt;This is a constraint I deliberately chose, and it simplifies a lot of things. During &lt;code&gt;nuxt generate&lt;/code&gt;, Nuxt fetches all content from Strapi's REST API. The output is static HTML. No runtime API calls. No CORS configuration. No authentication tokens in the browser. Strapi stays entirely behind the build process.&lt;/p&gt;

&lt;p&gt;This also means the content types are designed with forward compatibility in mind. Projects already have &lt;code&gt;slug&lt;/code&gt;, &lt;code&gt;fullDescription&lt;/code&gt;, and an SEO component which make it ready for detail pages when I decide to implement them, without any schema migration.&lt;/p&gt;
&lt;h3&gt;
  
  
  Custom Strapi composable instead of a module
&lt;/h3&gt;

&lt;p&gt;Rather than pulling in &lt;code&gt;@nuxtjs/strapi&lt;/code&gt;, I wrote a lightweight &lt;code&gt;useStrapi()&lt;/code&gt; composable that wraps Nuxt's &lt;code&gt;useFetch&lt;/code&gt; / &lt;code&gt;useAsyncData&lt;/code&gt; with the API token passed as a header. It's about 50 lines of TypeScript, it does exactly what the project needs, and it avoids shipping authentication code that will never run.&lt;/p&gt;

&lt;p&gt;Strapi v5 uses a flat API response format (no &lt;code&gt;data.attributes&lt;/code&gt; wrapping), and the types in &lt;code&gt;shared/types/strapi.ts&lt;/code&gt; reflect that directly. TypeScript strict mode is enforced so no &lt;code&gt;any&lt;/code&gt; and no index access without guards.&lt;/p&gt;
&lt;h3&gt;
  
  
  Tailwind v4 configured purely in CSS
&lt;/h3&gt;

&lt;p&gt;No &lt;code&gt;tailwind.config.ts&lt;/code&gt;. Tailwind v4 uses &lt;code&gt;@tailwindcss/vite&lt;/code&gt; as a Vite plugin and is configured via &lt;code&gt;@theme&lt;/code&gt; blocks in &lt;code&gt;app/assets/css/main.css&lt;/code&gt;. All design tokens (colors, gradients, shadows, animations) are CSS custom properties. Tailwind generates utility classes from them automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@theme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-space-black&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0a0a0f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-nebula-purple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#a855f7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--shadow-glow-purple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;168&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;247&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--animate-float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float&lt;/span&gt; &lt;span class="m"&gt;6s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt; &lt;span class="n"&gt;infinite&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 approach is cleaner, more composable, and doesn't require a JavaScript file for design decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance-first by design
&lt;/h3&gt;

&lt;p&gt;SSG eliminates server response time. The critical rendering path targets under 75kb of gzipped JavaScript (Nuxt + Vue + app code + i18n). Tsparticles and analytics are loaded client-only and deferred after hydration. The site is fully readable without JavaScript — animations and interactivity layer on top.&lt;/p&gt;

&lt;p&gt;Lighthouse targets: Performance &amp;gt; 95 on desktop, &amp;gt; 90 on mobile. Accessibility 100. SEO 100.&lt;/p&gt;

&lt;h3&gt;
  
  
  i18n architecture — hybrid approach
&lt;/h3&gt;

&lt;p&gt;UI strings (nav labels, button text, static messages) live in local JSON files consumed by &lt;code&gt;@nuxtjs/i18n&lt;/code&gt;. Editorial content (project descriptions, bio, job titles) is translated inside Strapi using its built-in i18n plugin. The build fetches each locale separately and generates separate URL paths: English at &lt;code&gt;/&lt;/code&gt;, French at &lt;code&gt;/fr/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This hybrid approach avoids duplicating every editorial string in JSON files while still keeping UI strings version-controlled and reviewable in the codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure
&lt;/h3&gt;

&lt;p&gt;Production runs on AWS: static output synced to a private S3 bucket, served through CloudFront with Origin Access Control. Strapi runs on an ARM EC2 instance (&lt;code&gt;t4g.micro&lt;/code&gt;) behind Caddy, containerized with Docker Compose. Media uploads go to a separate S3 bucket served via a second CloudFront distribution. All infrastructure is Terraform-managed.&lt;/p&gt;

&lt;p&gt;The CI/CD pipeline uses AWS OIDC which means no static credentials and no &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; stored as a secret. The GitHub Actions workflow assumes an IAM role via OIDC token, which I consider a baseline security requirement for any workflow touching cloud resources.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo &amp;amp; Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live portfolio&lt;/strong&gt;: &lt;a href="https://hugobollon.dev" rel="noopener noreferrer"&gt;hugobollon.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub repository&lt;/strong&gt;: &lt;a href="https://github.com/hbollon/portfolio-nuxt" rel="noopener noreferrer"&gt;github.com/hbollon/portfolio-nuxt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this project saved you some time or inspired your own build, a star on GitHub helps more than you might think — it signals to others that the project is worth looking at, and it keeps me motivated to improve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hbollon/portfolio-nuxt" rel="noopener noreferrer"&gt;⭐ Star the project on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Open Source
&lt;/h2&gt;

&lt;p&gt;Open source has always been a big part of how I work and build things. I maintain and contribute to several projects, including &lt;a href="https://github.com/hbollon/go-edlib" rel="noopener noreferrer"&gt;go-edlib&lt;/a&gt; (edit distance algorithms in Go) and &lt;a href="https://github.com/hbollon/jgo" rel="noopener noreferrer"&gt;jgo&lt;/a&gt; (a DAG JSON parsing library for Go). Sharing tools, improving them in public, and making them useful to others is something I naturally gravitate toward.&lt;/p&gt;

&lt;p&gt;This project follows the same logic. I built on top of countless open-source tools, and publishing it felt like a natural way to give something back. When I started, I couldn’t find a modern Nuxt 4 + Strapi portfolio that was both production-ready and reusable. Most examples were either too minimal or too tied to a specific personal setup.&lt;/p&gt;

&lt;p&gt;There’s also a practical benefit: public code tends to be better code. Knowing that someone might fork this and rely on it pushes me to keep things clean, documented, and maintainable.&lt;/p&gt;

&lt;p&gt;If you build something with it, I’d genuinely like to hear about it. Open an issue, leave a comment, or reach out.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/hbollon/portfolio-nuxt.git
&lt;span class="nb"&gt;cd &lt;/span&gt;portfolio-nuxt
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env.local
&lt;span class="c"&gt;# Set STRAPI_URL, STRAPI_TOKEN, NUXT_PUBLIC_SITE_URL&lt;/span&gt;
&lt;span class="c"&gt;# Leave blank to use fallback content for local evaluation&lt;/span&gt;

yarn &lt;span class="nb"&gt;install
&lt;/span&gt;yarn run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Strapi data model is documented in &lt;a href="https://github.com/hbollon/portfolio-nuxt/blob/main/specs/strapi-data-model.md" rel="noopener noreferrer"&gt;&lt;code&gt;specs/strapi-data-model.md&lt;/code&gt;&lt;/a&gt;. Content types, field definitions, required relations — it's all there.&lt;/p&gt;




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

&lt;p&gt;Rebuilding a portfolio is one of those tasks that's easy to justify putting off indefinitely. I finally did it because I had a clear goal: a maintainable stack, real content management, solid SEO, and something useful to other developers.&lt;/p&gt;

&lt;p&gt;The result is a Nuxt 4 + Strapi portfolio starter that I actually use in production, with deployment infrastructure I'd put in front of real traffic.&lt;/p&gt;

&lt;p&gt;If you're building your own portfolio, fork it, adapt it, break it, improve it. That's what it's there for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hbollon/portfolio-nuxt" rel="noopener noreferrer"&gt;github.com/hbollon/portfolio-nuxt&lt;/a&gt;&lt;/strong&gt; — contributions and feedback are welcome!&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Deploy your Pulumi project using Docker and Dagger.io</title>
      <dc:creator>Hugo Bollon</dc:creator>
      <pubDate>Wed, 14 Dec 2022 10:30:00 +0000</pubDate>
      <link>https://forem.com/camptocamp-ops/deploy-your-pulumi-project-using-docker-and-daggerio-2dig</link>
      <guid>https://forem.com/camptocamp-ops/deploy-your-pulumi-project-using-docker-and-daggerio-2dig</guid>
      <description>&lt;h2&gt;
  
  
  🕰️ In the previous episode
&lt;/h2&gt;

&lt;p&gt;In the first part of this Dagger's series, I showed you what's Dagger.io, what's the features of it and it's benefits against others ci/cd solutions and finally the very basis of Dagger.&lt;/p&gt;

&lt;p&gt;With this chapter, I will show you how we can overpower the CI/CD of any Pulumi project using Dagger.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧰 Pulumi - An amazing IaC tool
&lt;/h2&gt;

&lt;p&gt;First of all, I think that some of you may doesn't know what is Pulumi or even &lt;strong&gt;&lt;em&gt;IaC&lt;/em&gt;&lt;/strong&gt; (Infrastructure as Code) concept, so I will quickly present to you these two points.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure as Code
&lt;/h3&gt;

&lt;p&gt;Nowadays, IT extends to many areas, and so new needs are emerging leadingly to a necessity to adapt infrastructures in order to be able to support all of this.&lt;br&gt;
They also have seen their prerequisites evolve given their multiplication and their increasingly large sizes. As a result, companies started wanting to automate and simplify their infrastructures.&lt;/p&gt;

&lt;p&gt;To address this problem, Amazon unveiled in 2006 the concept of Infrastructure As Code (or &lt;strong&gt;&lt;em&gt;IaC&lt;/em&gt;&lt;/strong&gt;) allowing, on Amazon Web Services, the configuration of instances using computer code.&lt;br&gt;
It was a revolution for infrastructure management and, although limited at the time, this method was quickly adopted by the market.&lt;br&gt;
This event also coincides with the date of appearance of the DevOps movement in which it's part.&lt;/p&gt;

&lt;p&gt;Most of the infrastructure as code tools are based on the use of descriptor files to organize the code which avoids duplication between environments. Some advanced tools support variability, the use of outputs and even deployment to several providers simultaneously.&lt;/p&gt;

&lt;p&gt;There are three types of infrastructure as code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Imperative&lt;/strong&gt;: resources (instances, networks, etc.) are declared via a list of instructions in a defined order to obtain an expected result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functional&lt;/strong&gt;: unlike the imperative mode, the order of the instructions does not matter. The resources are defined in such a way that their final configuration is as expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Based on the environment&lt;/strong&gt;: the resources are declared in such a way that their state and their final configuration are consistent with the rest of the environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The advantages of IaC compared to traditional management are numerous, such as: cost reduction, the possibility of versioning the infrastructure, the speed of deployment and execution, the ability to collaborate, etc.&lt;br&gt;
It also allows complete automation, in fact, once the process is launched, there is no longer any need for human intervention. This advantage not only limits the risks due to human error and therefore increases reliability, but also allows teams to focus on projects and less on the deployment of applications.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pulumi
&lt;/h3&gt;

&lt;p&gt;Pulumi is an open-source IaC tool. It can be used to create, manage and deploy an infrastructure on many cloud provider like AWS, GCP, Scaleway, etc.&lt;br&gt;
It haves few strengths compared to others IaC solutions like &lt;em&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;/em&gt; for example. &lt;/p&gt;

&lt;p&gt;First of all, Pulumi support many programming languages like Go, TypeScript, Python, Java, F# and few others.&lt;br&gt;
This is a great advantage for Pulumi and one of the reasons why we begin to use it at &lt;em&gt;Camptocamp&lt;/em&gt; because the presence of a programming language like Go, rather than a configuration language like HCL (language used by &lt;em&gt;Terraform&lt;/em&gt;) allows much more flexibility and adaptability.&lt;/p&gt;

&lt;p&gt;Furthermore, Pulumi has new native providers like AWS, Google, and others in order to get new versions the same day they are released.&lt;br&gt;
Additionally, Pulumi also supports Terraform providers to maintain compatibility with any infrastructure built using Terraform.&lt;/p&gt;

&lt;p&gt;Another very interesting advantage is the support of what they call “&lt;em&gt;Dynamic Providers&lt;/em&gt;” which allows to easily extend an existing provider with new types of personalized resources and that by directly programming new CRUD operations.&lt;br&gt;
This can allow new resources to be added, for example, while adding complex migration or configuration logic.&lt;/p&gt;

&lt;p&gt;Finally, there are still many advantages to Pulumi such as the ease of carrying out tests thanks to the native frameworks of the programming languages provided for this usage, the presence of &lt;strong&gt;aliases&lt;/strong&gt; allowing a resource to be renamed while maintaining compatibility with the state of the infrastructure, better integrations with mainstream IDEs like VSCode, etc.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚡ Supercharge your Pulumi project thanks to Dagger
&lt;/h2&gt;

&lt;p&gt;Now that you know &lt;strong&gt;IaC&lt;/strong&gt;, &lt;strong&gt;Pulumi&lt;/strong&gt; and of course &lt;strong&gt;Dagger&lt;/strong&gt; (if not, you can check the first part of this blog series), we will see how we can create CI/CD pipelines for any Pulumi project using Dagger and CUE and finally how can we run them.&lt;br&gt;
For that, I will present to you the Dagger architecture we have built at Camptocamp, it was designed to be complete and reusable. It may be too complex for small projects but if you understand it you will normally be able to create your own !&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important note&lt;/strong&gt;: Initially Dagger.io was developed using &lt;a href="https://cuelang.org/" rel="noopener noreferrer"&gt;&lt;em&gt;Cuelang&lt;/em&gt;&lt;/a&gt;, an amazing and modern declarative language. Cue was also the only way to do pipelines with it. However, Dagger's team have recently transformed Dagger to be language agnostic.&lt;br&gt;
We now have different SDKs: Go (the main one), CUE, Node.js and Python.&lt;br&gt;
As of today, I advice you to choose the Go SDK if you don't really know which one to take. For the CUE one, I only recommend it to you if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You like declarative language like YAML&lt;/li&gt;
&lt;li&gt;You know CUE or want to learn it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the following chapters, I will present our implementation which is made in CUE. However, it will be easy to transpose it to other SDKs if you understand it. &lt;/p&gt;
&lt;h3&gt;
  
  
  📦 The Pulumi package
&lt;/h3&gt;

&lt;p&gt;In order to make a reusable and powerful Dagger project, we decided to create a "Pulumi" package which embeds our objects definitions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Docker image&lt;/li&gt;
&lt;li&gt;the container definition&lt;/li&gt;
&lt;li&gt;the command object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an exemple there is the command object definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pulumi/command.cue
package pulumi

import (
    "dagger.io/dagger"
    "universe.dagger.io/bash"
    "universe.dagger.io/docker"
)

#Command: self = {
    image?: docker.#Image
    name:   string
    args: [...string]
    env: [string]: string
    source: dagger.#FS
    input?: dagger.#FS

    _forceRun: bash.#RunSimple &amp;amp; {
        script: contents: "true"
        always: true
    }

    _container: #Container &amp;amp; {
        if self.image != _|_ {
            image: self.image
        }

        source: self.source

        command: {
            name: self.name
            args: self.args
        }

        env: self.env &amp;amp; {
            FORCE_RUN_HACK: "\(_forceRun.success)"
        }

        if self.input != _|_ {
            mounts: input: {
                type:     "fs"
                dest:     "/input"
                contents: self.input
            }
        }

        export: directories: "/output": _
    }

    output: _container.export.directories."/output"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top of this file, you can see the package definition and all our imports. &lt;code&gt;universe.dagger.io&lt;/code&gt; is a community repository where we can find pre-made package for Docker, bash, alpine, etc.&lt;br&gt;
Just after that, we start defining our &lt;code&gt;#Command&lt;/code&gt; object by adding public fields like command's name, args, custom Docker image (which is optional due to &lt;code&gt;?&lt;/code&gt; character, etc.&lt;br&gt;
We also define our unexported fields (which aren't accessible from outside the package) like the container definition which will run our command (the container object is defined in the &lt;code&gt;container.cue&lt;/code&gt; file in the same repository).&lt;/p&gt;

&lt;p&gt;This is one of the few definitions that we have in this package, I will not show you directly all of them since it's really specific to our implementation and it's not very relevant to explain how it's working.&lt;br&gt;
However, you can retrieve all of our source code here: &lt;a href="https://github.com/camptocamp/dagger-pulumi" rel="noopener noreferrer"&gt;https://github.com/camptocamp/dagger-pulumi&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So one last file from this pulumi package that we will see is the &lt;code&gt;main.cue&lt;/code&gt; one. It's where we defined all the Pulumi commands that we will use (using the &lt;code&gt;#Command&lt;/code&gt; object seen just before).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pulumi/main.cue
[...]
#Preview: self = {
    stack: string
    diff:  bool | *false

    #Command &amp;amp; {
        name: "preview"

        args: [
            "--stack",
            stack,
            "--save-plan",
            "/output/plan.json",

            if diff {
                "--diff"
            },
        ]
    }

    _file: core.#ReadFile &amp;amp; {
        input: self.output
        path:  "plan.json"
    }

    plan: _file.contents
}

#Update: {
    stack: string
    diff:  bool | *false
    plan:  string

    _file: core.#WriteFile &amp;amp; {
        input:    dagger.#Scratch
        path:     "plan.json"
        contents: planHere, you defined 
    }

    #Command &amp;amp; {
        name: "update"

        args: [
            "--stack",
            stack,
                        [...]
            if diff {
                "--diff"
            },

            "--skip-preview",
        ]

        input: _file.output
    }
}
[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I voluntary remove some parts of the file and leave only the definition for Pulumi preview and update commands in order to keep it simple.&lt;br&gt;
In the Pulumi context, preview command is used to compare the codebase which defines the desired infrastructure state with the actual one and preview all the potential changes if we apply the configuration. &lt;br&gt;
The update one, as it name says, update the deployed infrastructure to match the desired state.&lt;/p&gt;

&lt;p&gt;Using these definitions, we finally defined a &lt;code&gt;#Pulumi&lt;/code&gt; object in the &lt;code&gt;main.cue&lt;/code&gt; from the parent &lt;code&gt;ci&lt;/code&gt; package. &lt;br&gt;
It's composed by few attributes used to set our project environment like, for example, the Pulumi stack (workspace), the env variables, the authorization to run destructive actions through this CI, etc.&lt;br&gt;
We also have a list of all available commands construct using all &lt;code&gt;#Command&lt;/code&gt; objects defined earlier.&lt;/p&gt;

&lt;p&gt;Finally, the last step before we can be able to run our pipelines is to create the Dagger's plan.&lt;br&gt;
This is the most important part where all jobs are defined but it's really specific to the project for which it's built.&lt;br&gt;
I will present to you a plan from one of our projects where we used the pulumi package that I show you just before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import (
    "github.com/camptocamp/pulumi-aws-schweizmobil/ci"

    "dagger.io/dagger"
)

dagger.#Plan &amp;amp; {
    client: {
        env: {
            PULUMI_GOMAXPROCS?: string
            PULUMI_GOMEMLIMIT?: string
        }

        filesystem: {
            sources: read: {
                path:     "."
                contents: dagger.#FS

                exclude: [
                    ".*",
                ]
            }

            // TODO
            // Simplify once https://github.com/dagger/dagger/issues/2909 is fixed
            plan: read: {
                path:     "plan.json"
                contents: string
            }

            planWrite: write: {
                path:     "plan.json"
                contents: actions.preview.plan
            }
        }
    }

    #Pulumi: ci.#Pulumi &amp;amp; {
        env: {
            if client.env.PULUMI_GOMAXPROCS != _|_ {
                GOMAXPROCS: client.env.PULUMI_GOMAXPROCS
            }

            if client.env.PULUMI_GOMEMLIMIT != _|_ {
                GOMEMLIMIT: client.env.PULUMI_GOMEMLIMIT
            }
        }

        source: client.filesystem.sources.read.contents
        stack:  string
        diff:   bool
        update: plan: client.filesystem.plan.read.contents
        enableDestructiveActions: bool
    }

    actions: {
        #Pulumi.commands
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firstly, you can see at the begin of this file the import of the homemade &lt;code&gt;ci&lt;/code&gt; package (as a reminder, it is fully available &lt;a href="https://github.com/camptocamp/dagger-pulumi" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;br&gt;
After, we defined the &lt;code&gt;dagger.#Plan&lt;/code&gt; object which is composed by few parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the client config: this is where we will be able to set some runtime elements like: env variables, filesystems with read/write permissions, the sources location...&lt;/li&gt;
&lt;li&gt;the actions: this is all the possible jobs that you will be able to run using Dagger CLI or in your CI/CD environment, in our case we just use local variable &lt;code&gt;#Pulumi.commands&lt;/code&gt; which is built using the &lt;code&gt;#Pulumi&lt;/code&gt; object defined in the &lt;code&gt;ci&lt;/code&gt; package. &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🚀 Launch Dagger's jobs
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Locally using CLI
&lt;/h4&gt;

&lt;p&gt;As I told in the previous part of this blog series, Dagger can run any jobs locally thanks to his Dockerize design.&lt;br&gt;
You must have installed the Dagger's CLI for Cue SDK (check the first part of this series or &lt;a href="https://docs.dagger.io/sdk/cue/526369/install" rel="noopener noreferrer"&gt;the official documentation&lt;/a&gt; if it's not already done).&lt;/p&gt;

&lt;p&gt;Once ready, you can open your favorite terminal client located inside your project directory (composed by your Pulumi project and your Dagger plan) and run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dagger --plan=ci.cue do --with '#Pulumi: stack: "&amp;lt;your_stack&amp;gt;"' preview&lt;/code&gt; -&amp;gt; it will run a &lt;strong&gt;Pulumi preview&lt;/strong&gt; on the desired stack&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dagger --plan=ci.cue do --with '#Pulumi: stack: "&amp;lt;your_stack&amp;gt;", #Pulumi: diff: true, #Pulumi: enableDestructiveActions: true' update&lt;/code&gt; -&amp;gt; it will run a &lt;strong&gt;Pulumi update&lt;/strong&gt; on the desired stack&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Remotely using a CI/CD environment
&lt;/h4&gt;

&lt;p&gt;With Dagger you can also run your pipelines on every CI/CD environments! &lt;br&gt;
As a simple example, you can easily run these jobs on Github Actions:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pulumi-project&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dagger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&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;Clone repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&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;Install Dagger Engine&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;cd /usr/local&lt;/span&gt;
            &lt;span class="s"&gt;wget -O - https://dl.dagger.io/dagger/install.sh | sudo sh&lt;/span&gt;
            &lt;span class="s"&gt;cd -&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;Run Pulumi update using Dagger&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;dagger-cue --plan=ci.cue do update --log-format plain        &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note than the &lt;code&gt;--log-format plain&lt;/code&gt; flag is useful in order to have well formatted logs in the Github Action output.&lt;br&gt;
With this action, a &lt;code&gt;pulumi update&lt;/code&gt; will be launched at every push on the main branch using Dagger.&lt;/p&gt;

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

&lt;p&gt;If you have made it this far I thank you very much and I hope I have succeeded to give you a good overview of what is Dagger and how is it possible to exploit it in order to improve its practices in terms of CI/CD.&lt;/p&gt;

&lt;p&gt;From my point of view, Dagger is a very promising solution. Right now we can still feel Dagger's young age, indeed, it's still not perfect.&lt;br&gt;
In my opinion, each SDK should continue to develop and harmonize with each other. I also think that Dagger's execution performance and cache management should be improved (I'm basing myself on the 0.2 version of the engine since 0.3 is not yet available with the Cuelang SDK so maybe that's already better!).&lt;br&gt;
Nevertheless, the advantages of Dagger are already essential for me, such as the possibility of running its pipelines locally or even being able to run them remotely on a remote VM using local sources.&lt;/p&gt;

&lt;p&gt;To be followed very closely! 😉&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Use Docker to build better CI/CD pipelines with Dagger</title>
      <dc:creator>Hugo Bollon</dc:creator>
      <pubDate>Sun, 11 Dec 2022 05:00:00 +0000</pubDate>
      <link>https://forem.com/camptocamp-ops/use-docker-to-build-better-cicd-pipelines-with-dagger-4l4j</link>
      <guid>https://forem.com/camptocamp-ops/use-docker-to-build-better-cicd-pipelines-with-dagger-4l4j</guid>
      <description>&lt;p&gt;With the raises of DevOps practices, CI/CD (continuous integration &amp;amp; continuous deployment) takes a major place in every delivery workload.&lt;br&gt;
CI/CD allow organizations to build, test and finally ship their applications more quickly and efficiently. It's a modern set of practices which allows to automatically trigger build, test or others types of jobs when the changes to the codebase are done.&lt;/p&gt;

&lt;p&gt;In this quest of automation, we can use some CI/CD ecosystem like Github Actions, Gitlab-CI or many more. &lt;br&gt;
However, a very promising new solution open-source is born called Dagger.  &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%2Fb0j9iony7pwmmyb9o7cb.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%2Fb0j9iony7pwmmyb9o7cb.png" alt="Dagger's logo" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🤔 Dagger? What is it?
&lt;/h2&gt;

&lt;p&gt;Dagger.io is a brand-new programmable CI/CD engine which is open-source. It was created by Solomon Hykes, the founder of Docker.&lt;br&gt;
It's designed to use Buildkit from Docker in order to runs our pipelines inside containers. &lt;br&gt;
For those who don't know, Buildkit is an improved backend used by Docker to build images. It's way more efficient than the old legacy Docker's builder due to its improved caching system, the parallelization of build tasks and the support of new features in Dockerfile. &lt;/p&gt;

&lt;p&gt;Dagger is also programmable, so we must create our pipelines as code, Dagger himself is written in CUE (Cuelang), a Google's langage. &lt;/p&gt;

&lt;p&gt;CUE is an acronym of "Configure Unify Execute"  and so as its name suggests, it's not another general purpose language but a declarative one mainly used for data templating and validation, configuration or even code generation.&lt;/p&gt;

&lt;p&gt;It's basically an evolution of more lambda languages like YAML or JSON and one of the thing which make it way better and modern is the presence of a package manager.&lt;/p&gt;

&lt;p&gt;So going back to Dagger, you can use CUE to build your pipelines but you can also use the SDK available with multiples languages support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;NodeJS&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  ✨ Features and advantages
&lt;/h2&gt;

&lt;p&gt;Dagger, thanks to his innovative containerized design and architecture, leads to many advantages over more conventional CI/CD methods.&lt;br&gt;
As seen previously, Dagger works with Docker containers allowing it to be cross-compatible with every CI/CD runtime environment like Github Actions, Gitlab-CI, Travis-CI, etc.&lt;/p&gt;

&lt;p&gt;One of the other main strengths of Dagger is the ability to do local testing of our pipeline using the Dagger CLI. This is made possible by the Dockerized design of it which makes the development and testing processes way easier compared to conventional CI/CD solutions.&lt;br&gt;
Conversely, Dagger is also able to perform remote runs (directly on a self-hosted Github runner for example) with local sources.&lt;br&gt;
To do this it is possible to use the environment variable &lt;code&gt;DOCKER_HOST&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Dockerized design also allows pipelines made with Dagger devkit to be run in every CI/CD runtime environment like, for example, Github Action (using the &lt;a href="https://github.com/dagger/dagger-for-github" rel="noopener noreferrer"&gt;official Dagger Github&lt;/a&gt; Action from the marketplace).&lt;br&gt;
Furthermore, it can also be run independently of the architecture of the platform. The only requirement is the Docker ecosystem support. So it can be run on a managed runner (eg. Github Runners), a self-hosted runner, a local machine, a serverless compute instance, etc. &lt;/p&gt;

&lt;p&gt;Furthermore, Dagger has a solid caching system which caches every operation by default, but it's customizable.&lt;br&gt;
It helps to reduce execution time of CI/CD jobs after the first runs by caching some unchanged required files like: the downloaded dependencies, some built binaries or just some CI/CD engine stuff.&lt;/p&gt;

&lt;p&gt;Finally, Dagger is designed to be reusable thanks to his internal package manager (provided by the Cue language). Indeed, it's similar to the one of the language Golang.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package app

import (
    "dagger.io/dagger"
    "dagger.io/dagger/core"
    "universe.dagger.io/bash"
    "universe.dagger.io/docker"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you can see the import of some packages from the "universe". The Universe is a community package repository, all sources of these packages can be found &lt;a href="https://github.com/dagger/dagger/tree/v0.2.x/pkg/universe.dagger.io" rel="noopener noreferrer"&gt;here&lt;/a&gt;, you can also contribute to them. &lt;/p&gt;

&lt;h2&gt;
  
  
  ❓ How does it works?
&lt;/h2&gt;

&lt;p&gt;Dagger is language agnostic, the wish of the team behind is to allow developers to make their pipelines with the language of their choice.&lt;br&gt;
For that, Dagger use a specific architecture. The SDKs (Go, Cue, Node and Python) don't actually run your pipelines themself. Instead, they send pipeline definitions to the Dagger GraphQL API which will after trigger the Dagger Engine.&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%2Fv947b89r1fksfpwyy94x.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%2Fv947b89r1fksfpwyy94x.png" alt="Dagger's architecture" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dagger's architecture scheme. Ref: &lt;a href="https://dagger.io/blog/graphql" rel="noopener noreferrer"&gt;https://dagger.io/blog/graphql&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  🎮 Demo application using Cue SDK
&lt;/h2&gt;

&lt;p&gt;Dagger's team made a demo app with pipelines designed to execute build and test jobs. It's useful to discover and try Dagger. &lt;br&gt;
To use it, you must, of course, have Docker installed since Dagger will use Buildkit but you must also install the CLI (the CUE one is available here: &lt;a href="https://docs.dagger.io/sdk/cue/526369/install" rel="noopener noreferrer"&gt;https://docs.dagger.io/sdk/cue/526369/install&lt;/a&gt;)&lt;br&gt;
Once done, clone this repository: &lt;a href="https://github.com/dagger/todoapp/blob/main/dagger.cue" rel="noopener noreferrer"&gt;https://github.com/dagger/todoapp/blob/main/dagger.cue&lt;/a&gt;&lt;br&gt;
Finally, open a terminal inside this freshly cloned project and run &lt;code&gt;dagger-cue project update&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now you're ready to get your hands in Dagger, if you look at the &lt;code&gt;dagger.cue&lt;/code&gt; file you will see the &lt;em&gt;plan&lt;/em&gt;. &lt;br&gt;
A plan in Dagger orchestrates the Actions. It's the base component of your configuration.&lt;/p&gt;

&lt;p&gt;Within this plan we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;interact with the client filesystem

&lt;ul&gt;
&lt;li&gt;read files, usually the current directory as &lt;code&gt;.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;write files, usually the build output as &lt;code&gt;_build&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;read and define env variables&lt;/li&gt;
&lt;li&gt;declare jobs like dependencies update, build &amp;amp; test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is the one of this demo app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package todoapp

import (
    "dagger.io/dagger"

    "dagger.io/dagger/core"
    "universe.dagger.io/netlify"
    "universe.dagger.io/yarn"
)

dagger.#Plan &amp;amp; {
    actions: {
        // Load the todoapp source code 
        source: core.#Source &amp;amp; {
            path: "."
            exclude: [
                "node_modules",
                "build",
                "*.cue",
                "*.md",
                ".git",
            ]
        }

        // Build todoapp
        build: yarn.#Script &amp;amp; {
            name:   "build"
            source: actions.source.output
        }

        // Test todoapp
        test: yarn.#Script &amp;amp; {
            name:   "test"
            source: actions.source.output

            // This environment variable disables watch mode
            // in "react-scripts test".
            // We don't set it for all commands, because it causes warnings
            // to be treated as fatal errors.
            // See https://create-react-app.dev/docs/advanced-configuration
            container: env: CI: "true"
        }

        // Deploy todoapp
        deploy: netlify.#Deploy &amp;amp; {
            contents: actions.build.output
            site:     string | *"dagger-todoapp"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that three different jobs are defined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a build one which will compile the react app using yarn&lt;/li&gt;
&lt;li&gt;a test one&lt;/li&gt;
&lt;li&gt;a deploy one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To execute locally one of those, you can execute:  &lt;code&gt;dagger-cue do &amp;lt;job_name&amp;gt;&lt;/code&gt;.&lt;br&gt;
So to build the project you can do &lt;code&gt;dagger-cue do build&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⛏️ To go deeper
&lt;/h2&gt;

&lt;p&gt;I hope to have given you a good overview of Dagger with this first chapter or at least to have made you want to go further with it.&lt;br&gt;
Getting started with Dagger and CUE is not necessarily easy.&lt;br&gt;
It's still an extremely young product (we are currently in version &lt;em&gt;v0.2.36&lt;/em&gt;) and there is, therefore, still a lot of optimization work to be done as well as missing features.&lt;br&gt;
Keep in mind that Dagger is an open-source project available &lt;a href="https://github.com/dagger/dagger" rel="noopener noreferrer"&gt;on Github&lt;/a&gt; so don't hesitate to open issues or pull request if needed. &lt;/p&gt;

&lt;p&gt;In the next (coming soon) chapter we will see how to use Dagger to build a deployment workflow for a Pulumi project and how to manage secrets.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>How I deployed a serverless and high availability Blackbox Exporter on AWS Fargate</title>
      <dc:creator>Hugo Bollon</dc:creator>
      <pubDate>Mon, 04 Jul 2022 14:19:14 +0000</pubDate>
      <link>https://forem.com/camptocamp-ops/how-i-deployed-a-serverless-and-high-availability-blackbox-exporter-on-aws-fargate-37hh</link>
      <guid>https://forem.com/camptocamp-ops/how-i-deployed-a-serverless-and-high-availability-blackbox-exporter-on-aws-fargate-37hh</guid>
      <description>&lt;p&gt;At &lt;a href="https://www.camptocamp.com" rel="noopener noreferrer"&gt;&lt;em&gt;Camptocamp&lt;/em&gt;&lt;/a&gt;, we're using multiple Blackbox Exporters hosted in a few different cloud providers and world regions. We're using them to monitor availability and ssl certificate validity and expiration of many websites.&lt;br&gt;
They were all deployed inside Linux VMs provisioned by Terraform and configured by our Puppet infrastructure. However, in order to achieve more simplicity and high availability, we wanted to deploy containers instead of these VMs.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧐 Why a serverless approach with AWS Fargate
&lt;/h2&gt;

&lt;p&gt;AWS ECS (Elastic Container Service) is a fully managed, highly scalable and docker compatible container orchestration service.&lt;br&gt;
It is widely used to host microservice applications like webservers, APIs or machine learning applications.&lt;/p&gt;

&lt;p&gt;With ECS, you're free to choose between EC2 or Fargate instances to run your apps. &lt;br&gt;
Fargate is a serverless compute engine which allows you to just focus on building and deploying your apps by taking away all infrastructure deployments and maintenance. No need to worry about security or operating systems, AWS will handle that.&lt;br&gt;
On the other hand, EC2 is more flexible than Fargate and less expensive. It can also be interesting for some customers to manage the security themselves.&lt;/p&gt;

&lt;p&gt;In our case, we opted for a serverless approach using Fargate in order to take advantage of the simplicity of a managed infrastructure since for blackboxes we have no specific security constraints for the infrastructure.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧳 What I use
&lt;/h2&gt;

&lt;p&gt;To deploy an application on ECS using Fargate you will need three different components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;ECS Cluster&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;ECS Task Definition&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;One or more &lt;strong&gt;ECS Service&lt;/strong&gt;
The &lt;strong&gt;Task Definition&lt;/strong&gt; is a template where you define your application (Docker image, ressources requests, networking mode, etc.).
The &lt;strong&gt;Service&lt;/strong&gt; is the component that  will deploy our Fargate instance(s) based on our task definition(s) in the newly created &lt;strong&gt;Cluster&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At &lt;em&gt;Camptocamp&lt;/em&gt;, we're doing IAC (infrastructure as code) using mostly Terraform. In order to simplify the deployment of all the resources necessary for the implementation of these components, I created two distinct Terraform modules: one to create an &lt;strong&gt;ECS Cluster&lt;/strong&gt; and one to create &lt;strong&gt;Services&lt;/strong&gt; within an existing cluster.&lt;br&gt;
They have been designed to be flexible and reusable, and we will take a closer look at them to find out what they do and how they work.&lt;/p&gt;
&lt;h3&gt;
  
  
  ⚙️ Module: ECS Cluster
&lt;/h3&gt;

&lt;p&gt;Firstly, I created a module aiming to deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;strong&gt;ECS cluster&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;an associated &lt;strong&gt;VPC&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the necessary &lt;strong&gt;IAM roles&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;Cloudwatch Log Group&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;network components (internet gateway, subnets, routes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use this module, we must provide some inputs variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A project name&lt;/li&gt;
&lt;li&gt;A project environment (optional)&lt;/li&gt;
&lt;li&gt;A list of public subnets&lt;/li&gt;
&lt;li&gt;A list of private subnets&lt;/li&gt;
&lt;li&gt;A list of availability zones &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Link: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/camptocamp" rel="noopener noreferrer"&gt;
        camptocamp
      &lt;/a&gt; / &lt;a href="https://github.com/camptocamp/terraform-aws-ecs-cluster" rel="noopener noreferrer"&gt;
        terraform-aws-ecs-cluster
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Terraform module used to create a new AWS ECS cluster with VPC, IAM roles and networking components
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;terraform-aws-ecs-cluster&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Terraform module used to create a new AWS ECS cluster with VPC, IAM roles and networking components&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/camptocamp/terraform-aws-ecs-cluster" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  ⚙️ Module: ECS Service Fargate
&lt;/h3&gt;

&lt;p&gt;Then, this second module aims to deploy a Fargate &lt;strong&gt;Service&lt;/strong&gt; in an existing &lt;strong&gt;ECS Cluster&lt;/strong&gt; (in this case deployed with the previous module).&lt;br&gt;
It will also create everything necessary to be able to access our service. Here is the full list of resources that will be created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;ECS Fargate Service&lt;/strong&gt; with its needed &lt;strong&gt;Security Group&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;ALB&lt;/strong&gt; (Application Load Balancer) also with a &lt;strong&gt;Security Group&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Target Group&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;HTTP&lt;/strong&gt; and &lt;strong&gt;HTTPS Listeners&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;DNS Zone&lt;/strong&gt; and &lt;strong&gt;Record&lt;/strong&gt; to the &lt;strong&gt;ALB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;ACM Certificate&lt;/strong&gt; with validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once again, this module requires some variables to be used but this time the list is a little bit longer so here are just the most important ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An application name&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;ECS Cluster ID&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;ECS Task Definition&lt;/strong&gt; ressource (to define what will be deployed on this instance)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;DNS Zone&lt;/strong&gt; and &lt;strong&gt;Host&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;VPC's ID&lt;/strong&gt; and &lt;strong&gt;CIDR Blocks&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;An application port&lt;/li&gt;
&lt;li&gt;Public and private subnets ids &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Link:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/camptocamp" rel="noopener noreferrer"&gt;
        camptocamp
      &lt;/a&gt; / &lt;a href="https://github.com/camptocamp/terraform-aws-ecs-service-fargate" rel="noopener noreferrer"&gt;
        terraform-aws-ecs-service-fargate
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Terraform module used to create a new Fargate Service in an existing ECS cluster with networking components (ALB, Target Group, Listener)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;terraform-aws-ecs-service-fargate&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Terraform module used to create a new Fargate Service in an existing ECS cluster with networking components (ALB, Target Group, Listener)&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/camptocamp/terraform-aws-ecs-service-fargate" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  🎓 How to
&lt;/h2&gt;

&lt;p&gt;So, our use case is to have a serverless &lt;strong&gt;Blackbox Exporter&lt;/strong&gt; deployed on &lt;strong&gt;AWS ECS&lt;/strong&gt; using a &lt;strong&gt;Fargate&lt;/strong&gt; instance in the &lt;strong&gt;eu-west-1&lt;/strong&gt; region.&lt;br&gt;
Furthermore, it must be accessible only by https with a valid ssl certificate and with basic authentication.&lt;/p&gt;

&lt;p&gt;In order to achieve that, we must add a Nginx sidecar container which will handle basic auth and proxying of the traffic to the Blackbox for authenticated entities.&lt;/p&gt;

&lt;p&gt;There is a simple architecture diagram of what we will achieve:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04gh6atr753v3tb0ukl6.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%2F04gh6atr753v3tb0ukl6.png" alt="ECS Blackbox exporter architecture diagram" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we will begin by creating the ECS Cluster using &lt;a href="https://github.com/camptocamp/terraform-aws-ecs-cluster" rel="noopener noreferrer"&gt;terraform-aws-ecs-cluster module&lt;/a&gt;, so, with all nested resources (VPC, subnets, etc.).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# versions.tf&lt;/span&gt;

&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 4.0"&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;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.tf&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ecs-cluster"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git@github.com:camptocamp/terraform-aws-ecs-cluster.git"&lt;/span&gt;

  &lt;span class="nx"&gt;project_name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs-cluster-blackbox-exporters"&lt;/span&gt;
  &lt;span class="nx"&gt;project_environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;
  &lt;span class="nx"&gt;availability_zones&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eu-west-1a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1b"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;public_subnets&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.10.0/24"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;private_subnets&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.20.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.30.0/24"&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;As you can see, a minimum of two availability zones is required in order to create &lt;strong&gt;VPC&lt;/strong&gt; subnets. You also need to provide at least two public and two private cidr blocks.&lt;/p&gt;

&lt;p&gt;Now that we have declared the module which will create a fresh &lt;strong&gt;ECS cluster&lt;/strong&gt; with all networking stuff associated with it, we can create the &lt;strong&gt;Task Definition&lt;/strong&gt; of our Blackbox application task that we will need after to define the &lt;strong&gt;ECS Service&lt;/strong&gt;.&lt;br&gt;
A &lt;strong&gt;Task Definition&lt;/strong&gt; is a template where we define the containers that we will be executed on our &lt;strong&gt;ECS service&lt;/strong&gt; (docker image to run, port mapping, environments values, log configuration, etc.), the resources required (CPU / Memory), the network mode of the task (with Fargate we must use &lt;em&gt;awsvpc&lt;/em&gt; mode), and much more!&lt;/p&gt;

&lt;p&gt;So, as we saw earlier, we will need two containers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Blackbox-Exporter container&lt;/strong&gt; which will have port 9115 exposed but inaccessible from the outside of the cluster.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Nginx container&lt;/strong&gt; which will be exposed to the internet on port 80 with a basic authentication. It will forward authenticated users to the Blackbox container. We will use &lt;a href="https://hub.docker.com/r/beevelop/nginx-basic-auth/" rel="noopener noreferrer"&gt;this docker image&lt;/a&gt; which allows an easy configuration of basic auth using env vars.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will use the &lt;strong&gt;Cloudwatch Log Group&lt;/strong&gt; created by the ecs-cluster module for the logs of these two containers.&lt;br&gt;
Furthermore, we will also use IAM users created by the module for execution and task role ARNs of our Task Definition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.tf&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_task_definition"&lt;/span&gt; &lt;span class="s2"&gt;"blackbox_fargate_task"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;family&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blackbox-exporter-task"&lt;/span&gt;

  &lt;span class="nx"&gt;container_definitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;DEFINITION&lt;/span&gt;&lt;span class="sh"&gt;
  [
    {
      "name": "ecs-service-blackbox-prod-container",
      "image": "prom/blackbox-exporter:latest",
      "essential": true,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "${module.ecs-cluster.cloudwatch_log_group_id}",
          "awslogs-region": "eu-west-1",
          "awslogs-stream-prefix": "ecs-service-blackbox-exporter-prod"
        }
      },
      "portMappings": [
        {
          "containerPort": 9115
        }
      ],
      "cpu": 256,
      "memory": 512
    },
    {
      "name": "ecs-service-nginx-prod-container",
      "image": "beevelop/nginx-basic-auth:v2021.04.1",
      "essential": true,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "${module.ecs-cluster.cloudwatch_log_group_id}",
          "awslogs-region": "eu-west-1",
          "awslogs-stream-prefix": "ecs-service-nginx-exporter-prod"
        }
      },
      "environment": [
        {
          "name": "HTPASSWD",
          "value": "${var.blackbox_htpasswd}"
        },
        {
          "name": "FORWARD_HOST",
          "value": "localhost"
        },
        {
          "name": "FORWARD_PORT",
          "value": "9115"
        }
      ],
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 80,
          "protocol": "tcp"
        }
      ],
      "networkMode": "awsvpc"
    }
  ]
&lt;/span&gt;&lt;span class="no"&gt;  DEFINITION

&lt;/span&gt;  &lt;span class="nx"&gt;requires_compatibilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;network_mode&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"512"&lt;/span&gt;
  &lt;span class="nx"&gt;cpu&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"256"&lt;/span&gt;
  &lt;span class="nx"&gt;execution_role_arn&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_task_execution_role_arn&lt;/span&gt;
  &lt;span class="nx"&gt;task_role_arn&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_task_execution_role_arn&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs-service-blackbox-exporter-td"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&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;In this exemple, I get the htpasswd from a Terraform variable &lt;code&gt;var.blackbox_htpasswd&lt;/code&gt;. You can define it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# variables.tf&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"blackbox_htpasswd"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will need a &lt;strong&gt;DNS Zone&lt;/strong&gt; where the &lt;strong&gt;ECS Service&lt;/strong&gt; module will create the record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# dns.tf&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_zone"&lt;/span&gt; &lt;span class="s2"&gt;"alb_dns_zone"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example.com"&lt;/span&gt;
  &lt;span class="nx"&gt;delegation_set_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;Delegation_set_id&amp;gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Optionally, you can create a delegation set in your AWS account, if you don't already have one, and add a &lt;strong&gt;delegation set id&lt;/strong&gt; on your &lt;strong&gt;Route53 zone&lt;/strong&gt; resource in order to always have the same DNS servers.&lt;/p&gt;

&lt;p&gt;Finally, we can now create our ECS Service :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ecs-cluster-service-blackbox"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git@github.com:camptocamp/terraform-aws-ecs-service-fargate.git"&lt;/span&gt;

  &lt;span class="nx"&gt;app_name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs-service-blackbox"&lt;/span&gt;
  &lt;span class="nx"&gt;app_environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;
  &lt;span class="nx"&gt;dns_zone&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example.com"&lt;/span&gt;
  &lt;span class="nx"&gt;dns_host&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blackbox.example.com"&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr_blocks&lt;/span&gt;

  &lt;span class="nx"&gt;ecs_cluster_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_cluster_id&lt;/span&gt;
  &lt;span class="nx"&gt;task_definition&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_task_definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blackbox_fargate_task&lt;/span&gt;
  &lt;span class="nx"&gt;task_lb_container_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs-service-nginx-prod-container"&lt;/span&gt;
  &lt;span class="nx"&gt;task_lb_container_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;

  &lt;span class="nx"&gt;subnet_private_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets&lt;/span&gt;&lt;span class="p"&gt;.*.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_public_ids&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs-cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_subnets&lt;/span&gt;&lt;span class="p"&gt;.*.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;generate_public_ip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;aws_route53_zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb_dns_zone&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;As you can see, you must provide to the module some of the previously created resources including: vpc id and cidr_blocks, ECS cluster id, DNS zone, the task definition ressource and the subnets.&lt;br&gt;
You must also set on which container the load balancer will redirect requests and on which port.&lt;/p&gt;

&lt;p&gt;Once all your resources are properly configured, you can run a &lt;code&gt;terraform apply&lt;/code&gt; to create them.&lt;/p&gt;

&lt;p&gt;That's it 🥳! You now have a nice serverless Blackbox accessible on &lt;code&gt;blackbox.example.com&lt;/code&gt; with basic auth! 🎉&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>monitoring</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Introduction to string edit distance and Levenshtein implementation in Golang</title>
      <dc:creator>Hugo Bollon</dc:creator>
      <pubDate>Sun, 27 Sep 2020 15:28:35 +0000</pubDate>
      <link>https://forem.com/hbollon/introduction-to-string-edit-distance-and-levenshtein-implementation-in-golang-2l99</link>
      <guid>https://forem.com/hbollon/introduction-to-string-edit-distance-and-levenshtein-implementation-in-golang-2l99</guid>
      <description>&lt;p&gt;In many domains and applications, we are sometimes led to having to compare several words or phrases and quantify their similarity.&lt;/p&gt;

&lt;p&gt;A fairly popular notion for this is the edit distance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit distance? What is it?
&lt;/h2&gt;

&lt;p&gt;Edit distance is a metric used to quantify the dissimilarity between two strings by counting the amount of operation needed to transform one to another. Depending on the algorithm chosen, the operations allowed may include: addition, deletion, substitution and finally translation.&lt;/p&gt;

&lt;p&gt;This measurement can then be used to calculate a percentage of similarity between two strings with this formula:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(maxLength - distance) / maxLength&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Differents types of edit distance
&lt;/h3&gt;

&lt;p&gt;There are many algorithms for calculating edit distance of two distinct strings.&lt;/p&gt;

&lt;p&gt;Among them, the most famous are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Levenstein distance&lt;/strong&gt; allowing insertion, deletion and substitution. This is the most famous edit distance algorithm but despite what you may think, it is not always the most appropriate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Damerau-Levenstein distance&lt;/strong&gt; allowing addition, deletion, substitution and transposition. &lt;br&gt;
There are two different methods of this algorithm, OSA (Optimal string alignment distance) which allows only a transposition by substring and Adjacent Transpositions which allows it as much transposition as necessary but which requires a fixed alphabet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;LCS distance&lt;/strong&gt; (Longest Common Subsequence) allowing addition and deletion. It's based on Longest Common Subsequence algorithm.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hamming distance&lt;/strong&gt; only applicable on strings of the same length, it allows only character substitution. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Jaro distance&lt;/strong&gt; allowing only transposition and returning a normalized similarity index between 0 and 1.&lt;br&gt;
There is a variant called &lt;strong&gt;Jaro-Winkler&lt;/strong&gt; which takes into account the common prefix of the two strings and which gives it greater weight in the calculation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Golang implementation of all of these algorithms with Unicode compatibility are available here : &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hbollon/go-edlib" rel="noopener noreferrer"&gt;https://github.com/hbollon/go-edlib&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the rest of this course, we will focus on &lt;strong&gt;Levenstein&lt;/strong&gt; algorithm.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is it used for?
&lt;/h2&gt;

&lt;p&gt;Edit distance can be used in several application areas such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fuzzy search algorithms&lt;/li&gt;
&lt;li&gt;Correction of spelling mistakes&lt;/li&gt;
&lt;li&gt;Word completion&lt;/li&gt;
&lt;li&gt;DNA sequence alignment algorithms&lt;/li&gt;
&lt;li&gt;Pattern matching&lt;/li&gt;
&lt;li&gt;And many more!&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Levenstein distance
&lt;/h1&gt;

&lt;p&gt;Levensthein is one of the most known edit distance algorithm. It was created by the Soviet mathematician Vladimir Levenshtein, who considered this distance in 1965. It allows insertions, deletions or substitutions.&lt;/p&gt;

&lt;p&gt;For example, the Levenshtein distance between “kitten” and “sitting” is 3 since, at a minimum, 3 edits are required to change one into the other.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kitten → sitten (substitution of “s” for “k”)&lt;/li&gt;
&lt;li&gt;sitten → sittin (substitution of “i” for “e”)&lt;/li&gt;
&lt;li&gt;sittin → sitting (insertion of “g” at the end).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Definition
&lt;/h2&gt;

&lt;p&gt;The Levenshtein distance between two strings is defined by &lt;strong&gt;lev a,b(|a|,|b|)&lt;/strong&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;a,b&lt;/strong&gt; : two input strings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;|a|&lt;/strong&gt; : length of a&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;|b|&lt;/strong&gt; : length of b&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can find the Levenstein function on &lt;a href="https://en.wikipedia.org/wiki/Levenshtein_distance" rel="noopener noreferrer"&gt;Wikipedia&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%2Fi%2Fy9jaf28zhxktifeyn0nt.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%2Fi%2Fy9jaf28zhxktifeyn0nt.png" alt="Algo" width="441" height="73"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, it's not easy to understand at first. &lt;br&gt;&lt;/p&gt;

&lt;p&gt;Firstly, if one of the input strings is empty then the edit distance is the length of the other.&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%2Fi%2Fuw2rjbfd44haeissgimo.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%2Fi%2Fuw2rjbfd44haeissgimo.png" alt="Algo1" width="291" height="56"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Else, we will solve the Levenstein algorithm.&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%2Fi%2F56l23xfj9nvehrlp4o41.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%2Fi%2F56l23xfj9nvehrlp4o41.png" alt="Algo2" width="322" height="17"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this part, &lt;strong&gt;1(aᵢ≠bⱼ)&lt;/strong&gt; is equal to 0 when &lt;strong&gt;aᵢ≠bⱼ&lt;/strong&gt; and equal to 1 otherwise. It &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aᵢ refers to the character of string a at position i&lt;/li&gt;
&lt;li&gt;bⱼ refers to the character of string b at position j&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We want to check that these are not equal, because if they are equal, no edit is needed, so we shouldn't add 1 to the edit distance. However, if they aren't equal, we want to add 1 to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Algorithm and complexity
&lt;/h2&gt;

&lt;p&gt;There are several implementations of Levenshtein's algorithm, however, not all of them are efficient.&lt;br&gt;
We are going to look after the iterative method with full matrix.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;The matrix will contain distance between all prefixes of your input strings, then we can compute the matrix values in a dynamic programming style and finally find the distance between the two full strings as the last value computed.&lt;br&gt; &lt;/p&gt;

&lt;p&gt;For exemple, we will have this matrix for &lt;strong&gt;kitten&lt;/strong&gt; and &lt;strong&gt;sitting&lt;/strong&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%2Fi%2Fpniuptunu9hbstfzpi5n.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%2Fi%2Fpniuptunu9hbstfzpi5n.png" alt="Matrix" width="162" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see, the edit distance between &lt;strong&gt;kitten&lt;/strong&gt; and &lt;strong&gt;sitting&lt;/strong&gt; is 3. &lt;/p&gt;
&lt;h3&gt;
  
  
  Pascal algorithm
&lt;/h3&gt;

&lt;p&gt;On &lt;a href="https://en.wikipedia.org/wiki/Levenshtein_distance" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;, you can find a pseudo-code algorithm of this implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;LevenshteinDistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;s&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="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;t&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="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
  &lt;span class="c1"&gt;// for all i and j, d[i,j] will hold the Levenshtein distance between
&lt;/span&gt;  &lt;span class="c1"&gt;// the first i characters of s and the first j characters of t
&lt;/span&gt;  &lt;span class="n"&gt;declare&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;zero&lt;/span&gt;

  &lt;span class="c1"&gt;// source prefixes can be transformed into empty string by
&lt;/span&gt;  &lt;span class="c1"&gt;// dropping all characters
&lt;/span&gt;  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;d&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="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;

  &lt;span class="c1"&gt;// target prefixes can be reached from empty source prefix
&lt;/span&gt;  &lt;span class="c1"&gt;// by inserting every character
&lt;/span&gt;  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;n&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="n"&gt;from&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;m&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;s&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="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;substitutionCost&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;substitutionCost&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

          &lt;span class="n"&gt;d&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="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&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="c1"&gt;// deletion
&lt;/span&gt;                             &lt;span class="n"&gt;d&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="n"&gt;j&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;// insertion
&lt;/span&gt;                             &lt;span class="n"&gt;d&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&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="n"&gt;substitutionCost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// substitution
&lt;/span&gt;
  &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, if you understand what has been said so far, you should roughly understand it. :)&lt;/p&gt;

&lt;p&gt;This algorithm has a worst-case complexity of O(n*m), here a quick illustration of that: &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%2Fi%2F22kcgse2iemhdtfxmcva.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%2Fi%2F22kcgse2iemhdtfxmcva.png" alt="Alt Text" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Golang implementation
&lt;/h1&gt;

&lt;p&gt;It's all well and good to know all this, but how about implementing it in a real programming language?&lt;/p&gt;

&lt;p&gt;For this, we will use &lt;a href="https://golang.org/" rel="noopener noreferrer"&gt;Go&lt;/a&gt;. It's a modern and compiled language design by Google.&lt;/p&gt;

&lt;p&gt;Our implementation will be compatible with Unicode, so you will be able to put emoji or mathematical symbols for example in input strings!&lt;/p&gt;

&lt;p&gt;To begin, let's create the 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;func&lt;/span&gt; &lt;span class="n"&gt;LevenshteinDistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;str2&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&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;It will take two input strings and return an edit distance.&lt;br&gt;
Now, we must convert our strings into rune arrays to be Unicode compatible:&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;// Convert string parameters to rune arrays to be compatible with non-ASCII&lt;/span&gt;
&lt;span class="n"&gt;runeStr1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;rune&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;runeStr2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;rune&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Go, a rune is an alias for int32 (32-bit integer).&lt;/p&gt;

&lt;p&gt;Once that is done, we need to retrieve the length of these strings and start looking if these strings are equal or if any of them is empty.&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;// Get and store length of these strings&lt;/span&gt;
&lt;span class="n"&gt;runeStr1len&lt;/span&gt; &lt;span class="o"&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;runeStr1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;runeStr2len&lt;/span&gt; &lt;span class="o"&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;runeStr2&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;runeStr1len&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&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;runeStr2len&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;runeStr2len&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&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;runeStr1len&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&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;runeStr1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runeStr2&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="m"&gt;0&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;utils.Equal&lt;/strong&gt; is a utility function that I have defined to check equality of two rune arrays, you can retrieve it &lt;a href="https://github.com/hbollon/go-edlib/blob/master/internal/utils/utils.go" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Alternatively, you can compare directly strings input.&lt;/p&gt;

&lt;p&gt;To be more simple, our implementation will use a simple slice instead of a matrix, but the principle is the same.&lt;/p&gt;

&lt;p&gt;Let's create our distance array and initialize 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="c"&gt;// The slice size must be of the length of "runeStr1len+1"&lt;/span&gt;
&lt;span class="n"&gt;column&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="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runeStr1len&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&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;y&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;runeStr1len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's compute the column array with the Levenstein algorithm and return the distance between our input strings:&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;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;runeStr2len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
    &lt;span class="n"&gt;lastkey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;runeStr1len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;oldkey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;runeStr1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;runeStr2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// insert&lt;/span&gt;
                &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// delete&lt;/span&gt;
            &lt;span class="n"&gt;lastkey&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// substitution&lt;/span&gt;
        &lt;span class="n"&gt;lastkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oldkey&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;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;runeStr1len&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;utils.Min&lt;/strong&gt;, like previously, is a custom function, you can retrieve it &lt;a href="https://github.com/hbollon/go-edlib/blob/master/internal/utils/utils.go" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's it! Now you can compute the Levenstein distance between any words or sentences that you want! &lt;/p&gt;

&lt;p&gt;If you haven't made any mistakes, you should have 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="c"&gt;// LevenshteinDistance calculate the distance between two string&lt;/span&gt;
&lt;span class="c"&gt;// This algorithm allow insertions, deletions and substitutions to change one string to the second&lt;/span&gt;
&lt;span class="c"&gt;// Compatible with non-ASCII characters&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;LevenshteinDistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;str2&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Convert string parameters to rune arrays to be compatible with non-ASCII&lt;/span&gt;
    &lt;span class="n"&gt;runeStr1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;rune&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;runeStr2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;rune&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Get and store length of these strings&lt;/span&gt;
    &lt;span class="n"&gt;runeStr1len&lt;/span&gt; &lt;span class="o"&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;runeStr1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;runeStr2len&lt;/span&gt; &lt;span class="o"&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;runeStr2&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;runeStr1len&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&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;runeStr2len&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;runeStr2len&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&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;runeStr1len&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;runeStr1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runeStr2&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="m"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;column&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="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runeStr1len&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&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;y&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;runeStr1len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&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;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;runeStr2len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="n"&gt;lastkey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;runeStr1len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;oldkey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;runeStr1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;runeStr2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// insert&lt;/span&gt;
                    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// delete&lt;/span&gt;
                &lt;span class="n"&gt;lastkey&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// substitution&lt;/span&gt;
            &lt;span class="n"&gt;lastkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oldkey&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;column&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;runeStr1len&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Return the smallest integer among the two in parameters&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&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;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;a&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;b&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;a&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Compare two rune arrays and return if they are equals or not&lt;/span&gt;
&lt;span class="k"&gt;func&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;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;rune&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&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;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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;b&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="no"&gt;false&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&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;a&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;v&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;b&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="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&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="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;I sincerely hope you enjoyed this course and that it is not too messy! As a french student, this my first stories attempt so any feedback are welcome! :)&lt;/p&gt;

&lt;p&gt;I've done an edit distance and string comparison open-source library: &lt;a href="https://github.com/hbollon/go-edlib" rel="noopener noreferrer"&gt;Go-Edlib&lt;/a&gt;.&lt;br&gt;
It implements most popular edit distance algorithms and soon all of them! Currently, it includes: Levenshtein, LCS, Hamming, Damerau-Levenshtein (OSA and Adjacent transpositions algorithms), Jaro/Jaro-Winkler.&lt;br&gt;
All these algorithms have been implemented in such a way as to be fully compatible with Unicode&lt;/p&gt;

&lt;p&gt;It also includes fuzzy search algorithms based on edit distance and few others string comparisons functions.&lt;/p&gt;

&lt;p&gt;I'm actively looking for feedback and/or contributions to improve this library or have new functionality ideas to add!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>algorithms</category>
      <category>go</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>I created Golang edit distance &amp; string comparison library : Go-Edlib</title>
      <dc:creator>Hugo Bollon</dc:creator>
      <pubDate>Sat, 19 Sep 2020 14:50:31 +0000</pubDate>
      <link>https://forem.com/hbollon/i-created-golang-edit-distance-string-comparison-library-38p4</link>
      <guid>https://forem.com/hbollon/i-created-golang-edit-distance-string-comparison-library-38p4</guid>
      <description>&lt;h1&gt;
  
  
  Edit Distance?
&lt;/h1&gt;

&lt;p&gt;Edit Distance is a metric used to quantify the dissimilarity between two strings by counting the amount of operation needed to transform one to another. Depending on the algorithm chosen, the operations allowed may include: addition, deletion, substitution and finally translation.&lt;/p&gt;

&lt;p&gt;This measurement can then be used to calculate a percentage of similarity between two strings with this formula:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(maxLength - distance) / maxLength&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To illustrate this, we will look at the Levenshtein algorithm. Levensthein is one of the most known edit distance algorithm. It was created by the Soviet mathematician Vladimir Levenshtein, who considered this distance in 1965. It allows insertions, deletions or substitutions.&lt;/p&gt;

&lt;p&gt;For example, the Levenshtein distance between “kitten” and “sitting” is 3 since, at a minimum, 3 edits are required to change one into the other.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kitten → sitten (substitution of “s” for “k”)&lt;/li&gt;
&lt;li&gt;sitten → sittin (substitution of “i” for “e”)&lt;/li&gt;
&lt;li&gt;sittin → sitting (insertion of “g” at the end).&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What is it used for?
&lt;/h1&gt;

&lt;p&gt;Edit distance can be used in several application areas such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fuzzy search algorithms&lt;/li&gt;
&lt;li&gt;Correction of spelling mistakes&lt;/li&gt;
&lt;li&gt;Word completion&lt;/li&gt;
&lt;li&gt;DNA sequence alignment algorithms&lt;/li&gt;
&lt;li&gt;Pattern matching&lt;/li&gt;
&lt;li&gt;And many more!&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  So what is go-edlib?
&lt;/h1&gt;

&lt;p&gt;Go-Edlib is a new open-source library for Golang that implements most popular edit distance algorithms and soon all of them! Currently, it includes: Levenshtein, LCS, Hamming, Damerau-Levenshtein (OSA and Adjacent transpositions algorithms), Jaro/Jaro-Winkler.&lt;br&gt;
All these algorithms have been implemented in such a way as to be fully compatible with Unicode&lt;/p&gt;

&lt;p&gt;It also includes fuzzy search algorithms based on edit distance and few others string comparisons functions.&lt;/p&gt;

&lt;p&gt;Link: &lt;a href="https://github.com/hbollon/go-edlib" rel="noopener noreferrer"&gt;https://github.com/hbollon/go-edlib&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm actively looking for feedback and/or contributions to improve this library or to have new functionality ideas to add! :)&lt;br&gt;
PS: I would also like, if possible, opinions on the quality of my documentation and possibly points to improve&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>go</category>
      <category>algorithms</category>
      <category>contributorswanted</category>
    </item>
  </channel>
</rss>
