<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Raphaël Pinson</title>
    <description>The latest articles on DEV Community by Raphaël Pinson (@raphink).</description>
    <link>https://dev.to/raphink</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%2F59811%2Fcdcdbc95-1306-4455-9f79-fa032c300206.jpeg</url>
      <title>DEV Community: Raphaël Pinson</title>
      <link>https://dev.to/raphink</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/raphink"/>
    <language>en</language>
    <item>
      <title>How to Automatically Issue Badges for Instruqt Labs</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 17 Oct 2024 09:00:00 +0000</pubDate>
      <link>https://dev.to/raphink/how-to-automatically-issue-badges-for-instruqt-labs-18k5</link>
      <guid>https://dev.to/raphink/how-to-automatically-issue-badges-for-instruqt-labs-18k5</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9"&gt;first blog post&lt;/a&gt;, we talked about making labs fun and enjoyable by adding elements of gamification. Issuing badges is a fantastic way to motivate learners, giving them a sense of accomplishment for the skills they've gained, and Isovalent issues &lt;a href="https://www.credly.com/organizations/isovalent/badges" rel="noopener noreferrer"&gt;hundreds of them&lt;/a&gt; every month for the Cilium labs!&lt;/p&gt;

&lt;h1&gt;
  
  
  Issuing Credentials
&lt;/h1&gt;

&lt;p&gt;Obviously, issuing badges can be done manually, but this is not scalable or ideal for creating a seamless experience. So, let's automate it!&lt;/p&gt;

&lt;p&gt;Credly is a widely recognized provider of digital badges, so we will be using this solution to issue badges whenever a user finishes an Instruqt lab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Ffhqgqjpejwb8q0wf12fr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhqgqjpejwb8q0wf12fr.png" alt="Who doesn't love earning badges‽" width="554" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll be using Instruqt webhooks, coupled with the Credly API, to automatically issue badges when labs are completed. &lt;/p&gt;

&lt;p&gt;And thanks to Isovalent's open-sourced Go libraries for both &lt;a href="https://github.com/isovalent/instruqt-go" rel="noopener noreferrer"&gt;Instruqt&lt;/a&gt; and &lt;a href="https://github.com/isovalent/credly-go" rel="noopener noreferrer"&gt;Credly&lt;/a&gt; APIs, you will find this automation process smooth and straightforward.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/isovalent" rel="noopener noreferrer"&gt;
        isovalent
      &lt;/a&gt; / &lt;a href="https://github.com/isovalent/instruqt-go" rel="noopener noreferrer"&gt;
        instruqt-go
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Go library for the Instruqt API
    &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;instruqt-go&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://goreportcard.com/report/github.com/isovalent/instruqt-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fa8b48a9905bc6a9c6ec4c7cd213a8af24ec3cf219160155f7248e622ebe931c/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f69736f76616c656e742f696e7374727571742d676f" alt="Go Report Card"&gt;&lt;/a&gt;
&lt;a href="https://pkg.go.dev/github.com/isovalent/instruqt-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/133e5d01e865ae5bb4e38bb953b897bc2cce3453a28187bcfec71b6d88769b7b/68747470733a2f2f706b672e676f2e6465762f62616467652f6769746875622e636f6d2f69736f76616c656e742f696e7374727571742d676f2e737667" alt="Go Reference"&gt;&lt;/a&gt;
&lt;a href="https://github.com/isovalent/instruqt-goLICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/859a1a0bc85ce8bbd7a730a274fec5c9e77c4726ffdf6aa762a78685e26033a4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c75652e737667" alt="License: Apache 2.0"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;instruqt-go&lt;/code&gt; is a Go client library for interacting with the Instruqt platform. It provides a simple and convenient way to programmatically access Instruqt's APIs, manage content, retrieve user data and track information.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manage Instruqt Teams and Challenges&lt;/strong&gt;: Retrieve team information, challenges, and user progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;To install the &lt;code&gt;instruqt-go&lt;/code&gt; library, run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go get github.com/isovalent/instruqt-go&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Example Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-go notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;package&lt;/span&gt; main
&lt;span class="pl-k"&gt;import&lt;/span&gt; (
    &lt;span class="pl-s"&gt;"github.com/isovalent/instruqt-go/instruqt"&lt;/span&gt;
    &lt;span class="pl-s"&gt;"cloud.google.com/go/logging"&lt;/span&gt;
)

&lt;span class="pl-k"&gt;func&lt;/span&gt; &lt;span class="pl-en"&gt;main&lt;/span&gt;() {
    &lt;span class="pl-c"&gt;// Initialize the Instruqt client&lt;/span&gt;
    &lt;span class="pl-s1"&gt;client&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;instruqt&lt;/span&gt;.&lt;span class="pl-en"&gt;NewClient&lt;/span&gt;(&lt;span class="pl-s"&gt;"your-api-token"&lt;/span&gt;, &lt;span class="pl-s"&gt;"your-team-slug"&lt;/span&gt;)

    &lt;span class="pl-c"&gt;// Get all tracks&lt;/span&gt;
    &lt;span class="pl-s1"&gt;tracks&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;.&lt;span class="pl-en"&gt;GetTracks&lt;/span&gt;()

    &lt;span class="pl-c"&gt;// Add context to calls&lt;/span&gt;
    &lt;span class="pl-s1"&gt;ctx&lt;/span&gt;, &lt;span class="pl-s1"&gt;cancel&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;context&lt;/span&gt;.&lt;span class="pl-en"&gt;WithTimeout&lt;/span&gt;(&lt;span class="pl-s1"&gt;context&lt;/span&gt;.&lt;span class="pl-en"&gt;Background&lt;/span&gt;(), &lt;span class="pl-c1"&gt;5&lt;/span&gt;&lt;span class="pl-c1"&gt;*&lt;/span&gt;&lt;span class="pl-s1"&gt;time&lt;/span&gt;.&lt;span class="pl-c1"&gt;Second&lt;/span&gt;)
    &lt;span class="pl-k"&gt;defer&lt;/span&gt; &lt;span class="pl-en"&gt;cancel&lt;/span&gt;()
    
    &lt;span class="pl-s1"&gt;clientWithTimeout&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;.&lt;span class="pl-en"&gt;WithContext&lt;/span&gt;(&lt;span class="pl-s1"&gt;ctx&lt;/span&gt;)
    &lt;span class="pl-s1"&gt;userInfo&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;clientWithTimeout&lt;/span&gt;.&lt;span class="pl-en"&gt;GetUserInfo&lt;/span&gt;(&lt;span class="pl-s"&gt;"user-id"&lt;/span&gt;)

    &lt;span class="pl-c"&gt;// Attach a logger&lt;/span&gt;
    &lt;span class="pl-s1"&gt;logClient&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;logging&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/isovalent/instruqt-go" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/isovalent" rel="noopener noreferrer"&gt;
        isovalent
      &lt;/a&gt; / &lt;a href="https://github.com/isovalent/credly-go" rel="noopener noreferrer"&gt;
        credly-go
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Go library for the Credly API
    &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;credly-go&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://goreportcard.com/report/github.com/isovalent/credly-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/03c4cbc4215dd3733bb43dc9dde35733ad89e284ea906811f2fb7c8c6e5140f9/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f69736f76616c656e742f637265646c792d676f" alt="Go Report Card"&gt;&lt;/a&gt;
&lt;a href="https://pkg.go.dev/github.com/isovalent/credly-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/751d47251770c3f3894e84072b06167a6f584bdfb17ac728f1054b140ef5d4d3/68747470733a2f2f706b672e676f2e6465762f62616467652f6769746875622e636f6d2f69736f76616c656e742f637265646c792d676f2e737667" alt="Go Reference"&gt;&lt;/a&gt;
&lt;a href="https://github.com/isovalent/credly-goLICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/859a1a0bc85ce8bbd7a730a274fec5c9e77c4726ffdf6aa762a78685e26033a4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c75652e737667" alt="License: Apache 2.0"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;credly-go&lt;/code&gt; is a Go client library for interacting with the Credly platform. It provides a simple and convenient way to programmatically access Credly's APIs and handle badges and templates.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Badge Management&lt;/strong&gt;: Issue, retrieve, and manage badges using the Credly API.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;To install the &lt;code&gt;credly-go&lt;/code&gt; library, run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go get github.com/isovalent/credly-go&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Example Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-go notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;package&lt;/span&gt; main

&lt;span class="pl-k"&gt;import&lt;/span&gt; (
    &lt;span class="pl-s"&gt;"github.com/isovalent/credly-go/credly"&lt;/span&gt;
)

&lt;span class="pl-k"&gt;func&lt;/span&gt; &lt;span class="pl-en"&gt;main&lt;/span&gt;() {
    &lt;span class="pl-c"&gt;// Initialize the Credly client&lt;/span&gt;
    &lt;span class="pl-s1"&gt;client&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;credly&lt;/span&gt;.&lt;span class="pl-en"&gt;NewClient&lt;/span&gt;(&lt;span class="pl-s"&gt;"your-api-token"&lt;/span&gt;, &lt;span class="pl-s"&gt;"your-credly-org"&lt;/span&gt;)

    &lt;span class="pl-c"&gt;// Get all badges for user joe@example.com&lt;/span&gt;
    &lt;span class="pl-s1"&gt;badges&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;.&lt;span class="pl-en"&gt;GetBadges&lt;/span&gt;(&lt;span class="pl-s"&gt;"joe@example.com"&lt;/span&gt;)
}&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;We welcome contributions! Please follow these steps to contribute:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fork the repository.&lt;/li&gt;
&lt;li&gt;Create a new branch with your feature or bug fix.&lt;/li&gt;
&lt;li&gt;Make your changes and add tests.&lt;/li&gt;
&lt;li&gt;Submit a pull request with a detailed description of your changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Running Tests&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;To run the tests, use:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go &lt;span class="pl-c1"&gt;test&lt;/span&gt; ./...&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Make sure…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/isovalent/credly-go" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;In this post, we'll take you step by step through the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting up the environment and harnessing Google Cloud Functions.&lt;/li&gt;
&lt;li&gt;Initializing imports, constants, and setting up secret environment variables.&lt;/li&gt;
&lt;li&gt;Implementing the webhook and explaining each step.&lt;/li&gt;
&lt;li&gt;Setting up the webhook in Instruqt and adding signature verification to secure it.&lt;/li&gt;
&lt;li&gt;Testing locally using Docker and Docker Compose.&lt;/li&gt;
&lt;li&gt;Deploying the webhook and required secrets to Google Cloud Platform.&lt;/li&gt;
&lt;li&gt;Wrapping up with some final considerations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;p&gt;As for the first blog post, you will need an Instruqt account (with an API key) and a Google Cloud project.&lt;/p&gt;

&lt;p&gt;In addition, you will also need a Credly account with an API key this time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Environment
&lt;/h2&gt;

&lt;p&gt;First, create a directory for your function and initialize the Go environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;instruqt-webhook
&lt;span class="nb"&gt;cd &lt;/span&gt;instruqt-webhook

go mod init example.com/labs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just as in the &lt;a href="https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9"&gt;first post&lt;/a&gt;, we create a &lt;code&gt;cmd&lt;/code&gt; directory so we can build and test the function locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;cmd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;main.go&lt;/code&gt; file in that directory, with the following content:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="c"&gt;// Blank-import the function package so the init() runs&lt;/span&gt;
    &lt;span class="c"&gt;// Adapt if you replaced example.com earlier&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"example.com/labs"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/GoogleCloudPlatform/functions-framework-go/funcframework"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Use PORT environment variable, or default to 8080.&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"8080"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;envPort&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;envPort&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;envPort&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;funcframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"funcframework.Start: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to the &lt;code&gt;instruqt-webhook&lt;/code&gt; directory, create a file named &lt;code&gt;webhook.go&lt;/code&gt; to contain the function logic. This file will serve as the webhook handler for incoming events from Instruqt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Basics
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;webhook.go&lt;/code&gt;, begin by adding the necessary imports, constants, and initializing 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;package&lt;/span&gt; &lt;span class="n"&gt;labs&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/GoogleCloudPlatform/functions-framework-go/functions"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/isovalent/instruqt-go/instruqt"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/isovalent/credly-go/credly"&lt;/span&gt;

&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;functions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"InstruqtWebhookCatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruqtWebhookCatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;instruqtTeam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"yourInstruqtTeam"&lt;/span&gt;   &lt;span class="c"&gt;// Replace with your own team name&lt;/span&gt;
    &lt;span class="n"&gt;credlyOrg&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"yourCredlyOrg"&lt;/span&gt;      &lt;span class="c"&gt;// Replace with your own credly organization ID&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing the Webhook Receiver
&lt;/h2&gt;

&lt;p&gt;Now, let's write the &lt;code&gt;instruqtWebhookCatch&lt;/code&gt; function to receive the event.&lt;/p&gt;

&lt;p&gt;We will take advantage of the methods provided by the Isovalent &lt;code&gt;instruqt-go&lt;/code&gt; library to manage the Instruqt webhook:&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;instruqtWebhookCatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;webhookSecret&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSTRUQT_WEBHOOK_SECRET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wbHandler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processWebhook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webhookSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wbHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&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 function works as a proxy between the HTTP connection handler provided by the Google Cloud Functions framework and the &lt;code&gt;instruqt.HandleWebhook&lt;/code&gt; method provided by Isovalent's library to manage the Svix webhook.&lt;/p&gt;

&lt;p&gt;It allows us to set up a webhook manager by passing the webhook's secret. We will see later where to find the value for the webhook secret.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;instruqt.HandleWebhook&lt;/code&gt; method will automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the webhook signature using svix.&lt;/li&gt;
&lt;li&gt;Parse the incoming event payload.&lt;/li&gt;
&lt;li&gt;Check if the event is valid.&lt;/li&gt;
&lt;li&gt;Retrieve the information into an instruqt.WebhookEvent structure.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 4: The &lt;code&gt;processWebhook()&lt;/code&gt; Function
&lt;/h2&gt;

&lt;p&gt;Next, we need to implement the &lt;code&gt;processWebhook&lt;/code&gt; function, where our logic will be placed.&lt;/p&gt;

&lt;p&gt;This function will receive 3 parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the HTTP connection handlers (&lt;code&gt;http.ResponseWriter&lt;/code&gt; and &lt;code&gt;*http.Request&lt;/code&gt;) inherited from the GCP Function handler;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;instruqt.Webhook&lt;/code&gt; structure parsed by &lt;code&gt;instruqt.HandleWebhook&lt;/code&gt; and passed down to us.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the complete implementation:&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;processWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt; &lt;span class="n"&gt;instruqt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebhookEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Return early if the event type is not track.completed&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"track.completed"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNoContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Setup the Instruqt client&lt;/span&gt;
    &lt;span class="n"&gt;instruqtToken&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSTRUQT_TOKEN"&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;instruqtToken&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;instruqtClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instruqtToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruqtTeam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Setup the Credly client&lt;/span&gt;
    &lt;span class="n"&gt;credlyToken&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREDLY_TOKEN"&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;credlyToken&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;credlyClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;credly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credlyToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credlyOrg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Get user info from Instruqt&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqtClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetUserInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get user info: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Get track details to extract badge template ID from tags&lt;/span&gt;
    &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqtClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetTrackById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrackId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get track info: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Extract badge template ID from track tags&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;templateId&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&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;track&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrackTags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Use strings.Split to parse the tag and extract the badge template ID&lt;/span&gt;
        &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="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;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;parts&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="s"&gt;"badge"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;templateId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&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="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;templateId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No badge template ID found for track %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrackId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Issue badge through Credly&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;badgeErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;credlyClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssueBadge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;templateId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// Check if the badge has already been issued&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;badgeErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;badgeErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;credly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrBadgeAlreadyIssued&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Badge already issued for %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusConflict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to issue badge: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;badgeErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if the event is of type track.completed, exit otherwise.&lt;/li&gt;
&lt;li&gt;Instantiate Instruqt and Credly clients using environment variables for the tokens.&lt;/li&gt;
&lt;li&gt;Retrieve user information from the Instruqt API. This requires to ensure that Instruqt has that information. See the first blog post to find how to do that with a proxy.&lt;/li&gt;
&lt;li&gt;Get track information from Instruqt. We will use set a badge: special tag on the track to store the Credly badge ID to issue.&lt;/li&gt;
&lt;li&gt;Parse track tags to find the badge template ID.&lt;/li&gt;
&lt;li&gt;Issue the badge using the Credly library.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting Up the Webhook on Instruqt
&lt;/h2&gt;

&lt;p&gt;To enable Instruqt to call your webhook, navigate to the Instruqt UI, go to Settings -&amp;gt; Webhooks, and click "Add Endpoint" to set up a new webhook that points to your Google Cloud Function URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F962yseu7n7pgiwbauwyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F962yseu7n7pgiwbauwyk.png" alt="Create Webhook" width="800" height="882"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;code&gt;track.completed&lt;/code&gt; in the list of events to fire up this endpoint.&lt;/p&gt;

&lt;p&gt;Since we'll be hosting the function on Google Cloud Functions, the URL will be in the form &lt;code&gt;https://&amp;lt;zone&amp;gt;-&amp;lt;project&amp;gt;.cloudfunctions.net/&amp;lt;name&amp;gt;&lt;/code&gt;. For example, if your function is called &lt;code&gt;instruqt-webhook&lt;/code&gt; and is deployed in the &lt;code&gt;labs&lt;/code&gt; GCP project in the &lt;code&gt;europe-west1&lt;/code&gt; zone, then the URL will be &lt;code&gt;https://europe-west1-labs.cloudfunctions.net/instruqt-webhook&lt;/code&gt;. If in doubt, put a fake URL and you can modify it later.&lt;/p&gt;

&lt;p&gt;Create "Create", then locate the "Signing secret" field to the right side of the panel and copy its value.&lt;/p&gt;

&lt;p&gt;Export it in your terminal as the &lt;code&gt;INSTRUQT_WEBHOOK_SECRET&lt;/code&gt; value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;INSTRUQT_WEBHHOOK_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;whsec_v/somevalueCopiedFromUi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use it to create a new GCP secret called &lt;code&gt;instruqt-webhook-secret&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTRUQT_WEBHHOOK_SECRET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | gcloud secrets create instruqt-webhook-secret &lt;span class="nt"&gt;--data-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it the proper permissions to be usable in your function (see first blog post for details):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PROJECT_NUMBER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud projects describe &lt;span class="si"&gt;$(&lt;/span&gt;gcloud config get-value project&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"value(projectNumber)"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
gcloud secrets add-iam-policy-binding instruqt-webhook-secret &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--member&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"serviceAccount:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NUMBER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-compute@developer.gserviceaccount.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"roles/secretmanager.secretAccessor"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also create a secret for your Credly token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CREDLY_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yourCredlyToken
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CREDLY_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | gcloud secrets create credly-token &lt;span class="nt"&gt;--data-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-
gcloud secrets add-iam-policy-binding credly-token &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--member&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"serviceAccount:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NUMBER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-compute@developer.gserviceaccount.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"roles/secretmanager.secretAccessor"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing the Code
&lt;/h2&gt;

&lt;p&gt;Let's check that this function builds and runs fine.&lt;/p&gt;

&lt;p&gt;First, update your &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; files with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get ./...
go mod tidy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, run the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FUNCTION_TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;InstruqtWebhookCatch go run ./cmd/main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function should compile and run fine. You can try sending queries to it on &lt;code&gt;localhost:8080&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect to get an error since the Svix webhook authentication is not set up properly in the payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 405 Method Not Allowed
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Tue, 08 Oct 2024 13:20:47 GMT
Content-Length: 23

Invalid request method
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would be possible to emulate this, but it's a bit complex, so let's just deploy to GCP now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternative testing: using Docker
&lt;/h2&gt;

&lt;p&gt;If you'd like to use Docker to test your function locally, you can create a &lt;code&gt;Dockerfile&lt;/code&gt; in your current directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.23&lt;/span&gt;

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

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; myapp ./cmd/main.go

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; DEV=true&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=8080&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; $PORT&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["./myapp"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;docker-compose.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;INSTRUQT_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${INSTRUQT_WEBHOOK_SECRET}&lt;/span&gt;
      &lt;span class="na"&gt;INSTRUQT_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${INSTRUQT_TOKEN}&lt;/span&gt;
      &lt;span class="na"&gt;CREDLY_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CREDLY_TOKEN}&lt;/span&gt;
      &lt;span class="na"&gt;FUNCTION_TARGET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InstruqtWebhookCatch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, build and launch your container:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;And you can send requests to &lt;code&gt;localhost:8080&lt;/code&gt; just the same as before!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the Function
&lt;/h2&gt;

&lt;p&gt;You can then deploy the function (adapt the region if needed), giving it access to all three secret values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud functions deploy &lt;span class="s2"&gt;"instruqt-webhook"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gen2&lt;/span&gt; &lt;span class="nt"&gt;--runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;go122 &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-west1 &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--entry-point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"InstruqtWebhookCatch"&lt;/span&gt; &lt;span class="nt"&gt;--trigger-http&lt;/span&gt; &lt;span class="nt"&gt;--allow-unauthenticated&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-secrets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"INSTRUQT_WEBHOOK_SECRET=instruqt-webhook-secret:latest"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-secrets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"INSTRUQT_TOKEN=instruqt-token:latest"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-secrets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"CREDLY_TOKEN=credly-token:latest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will upload and build your project, and return the URL to access the function.&lt;/p&gt;

&lt;p&gt;If necessary, update the URL in your Instruqt webhook configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Now for the moment of truth: testing!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a badge on Credly. Publish it and copy its template ID.&lt;/li&gt;
&lt;li&gt;Add a tag to the Instruqt track you want to associate the badge with. Name the tag &lt;code&gt;badge:&amp;lt;template_ID&amp;gt;&lt;/code&gt;, replacing &lt;code&gt;template_ID&lt;/code&gt; with the ID you just copied.&lt;/li&gt;
&lt;li&gt;Publish the track.&lt;/li&gt;
&lt;li&gt;Take the track and complete it!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should get the badge in your email!&lt;/p&gt;

&lt;h1&gt;
  
  
  Further Considerations
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User Information&lt;/strong&gt;: Make sure you read the &lt;a href="https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9"&gt;first blog post&lt;/a&gt; to understand how to send user information to Instruqt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make it worth it!&lt;/strong&gt;: Getting badges is fun, but it's better if users deserve them. Consider adding exam steps to your tracks to make earning the badges a challenge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiting and Retries&lt;/strong&gt;: Consider rate limiting incoming webhook requests to prevent abuse and adding retry logic to handle temporary failures when interacting with Credly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage more Events&lt;/strong&gt;: This webhook manager only manages &lt;code&gt;track.completed&lt;/code&gt; events. You can extend it to do a lot more things with all the events provided by Instruqt! I typically like to capture lots of events to send them to Slack for better visibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt;: Consider adding more logging (for example using the GCP logging library) to the code.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>instruqt</category>
      <category>credly</category>
      <category>devrel</category>
      <category>go</category>
    </item>
    <item>
      <title>Streamlining Access to Embedded Instruqt Labs</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 03 Oct 2024 11:58:29 +0000</pubDate>
      <link>https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9</link>
      <guid>https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9</guid>
      <description>&lt;p&gt;How do you teach a very technical topic to prospects and customers? How do you make it a smooth ride?&lt;/p&gt;

&lt;p&gt;At Isovalent, we're passionate about making the learning experience as seamless as possible for our users. Isovalent are the creators of Cilium, the &lt;em&gt;de facto&lt;/em&gt; cloud networking platform for Kubernetes. While we love networking and security, we appreciate folks might find it a difficult topic. We thought we would make learning Kubernetes networking fun, so we make it a point to gamify the learning experience.&lt;br&gt;
Instruqt provides a great platform to build hands-on labs that can be both technically advanced and engaging for users.&lt;/p&gt;

&lt;p&gt;We also believe the user experience should be smooth and the processes fully automated.&lt;br&gt;
Fortunately, a lot can be done by leveraging the &lt;a href="https://api-docs.instruqt.com/" rel="noopener noreferrer"&gt;Instruqt graphQL API&lt;/a&gt;.&lt;br&gt;
To that purpose, we wrote our own &lt;code&gt;instruqt-go&lt;/code&gt; library, which we've decided to open source. The library is designed to help developers automate and integrate with the Instruqt platform with ease.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/isovalent" rel="noopener noreferrer"&gt;
        isovalent
      &lt;/a&gt; / &lt;a href="https://github.com/isovalent/instruqt-go" rel="noopener noreferrer"&gt;
        instruqt-go
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Go library for the Instruqt API
    &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;instruqt-go&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://goreportcard.com/report/github.com/isovalent/instruqt-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fa8b48a9905bc6a9c6ec4c7cd213a8af24ec3cf219160155f7248e622ebe931c/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f69736f76616c656e742f696e7374727571742d676f" alt="Go Report Card"&gt;&lt;/a&gt;
&lt;a href="https://pkg.go.dev/github.com/isovalent/instruqt-go" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/133e5d01e865ae5bb4e38bb953b897bc2cce3453a28187bcfec71b6d88769b7b/68747470733a2f2f706b672e676f2e6465762f62616467652f6769746875622e636f6d2f69736f76616c656e742f696e7374727571742d676f2e737667" alt="Go Reference"&gt;&lt;/a&gt;
&lt;a href="https://github.com/isovalent/instruqt-goLICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/859a1a0bc85ce8bbd7a730a274fec5c9e77c4726ffdf6aa762a78685e26033a4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c75652e737667" alt="License: Apache 2.0"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;instruqt-go&lt;/code&gt; is a Go client library for interacting with the Instruqt platform. It provides a simple and convenient way to programmatically access Instruqt's APIs, manage content, retrieve user data and track information.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manage Instruqt Teams and Challenges&lt;/strong&gt;: Retrieve team information, challenges, and user progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;To install the &lt;code&gt;instruqt-go&lt;/code&gt; library, run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;go get github.com/isovalent/instruqt-go&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Example Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-go notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;package&lt;/span&gt; main
&lt;span class="pl-k"&gt;import&lt;/span&gt; (
    &lt;span class="pl-s"&gt;"github.com/isovalent/instruqt-go/instruqt"&lt;/span&gt;
    &lt;span class="pl-s"&gt;"cloud.google.com/go/logging"&lt;/span&gt;
)

&lt;span class="pl-k"&gt;func&lt;/span&gt; &lt;span class="pl-en"&gt;main&lt;/span&gt;() {
    &lt;span class="pl-c"&gt;// Initialize the Instruqt client&lt;/span&gt;
    &lt;span class="pl-s1"&gt;client&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;instruqt&lt;/span&gt;.&lt;span class="pl-en"&gt;NewClient&lt;/span&gt;(&lt;span class="pl-s"&gt;"your-api-token"&lt;/span&gt;, &lt;span class="pl-s"&gt;"your-team-slug"&lt;/span&gt;)

    &lt;span class="pl-c"&gt;// Get all tracks&lt;/span&gt;
    &lt;span class="pl-s1"&gt;tracks&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;.&lt;span class="pl-en"&gt;GetTracks&lt;/span&gt;()

    &lt;span class="pl-c"&gt;// Add context to calls&lt;/span&gt;
    &lt;span class="pl-s1"&gt;ctx&lt;/span&gt;, &lt;span class="pl-s1"&gt;cancel&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;context&lt;/span&gt;.&lt;span class="pl-en"&gt;WithTimeout&lt;/span&gt;(&lt;span class="pl-s1"&gt;context&lt;/span&gt;.&lt;span class="pl-en"&gt;Background&lt;/span&gt;(), &lt;span class="pl-c1"&gt;5&lt;/span&gt;&lt;span class="pl-c1"&gt;*&lt;/span&gt;&lt;span class="pl-s1"&gt;time&lt;/span&gt;.&lt;span class="pl-c1"&gt;Second&lt;/span&gt;)
    &lt;span class="pl-k"&gt;defer&lt;/span&gt; &lt;span class="pl-en"&gt;cancel&lt;/span&gt;()
    
    &lt;span class="pl-s1"&gt;clientWithTimeout&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;.&lt;span class="pl-en"&gt;WithContext&lt;/span&gt;(&lt;span class="pl-s1"&gt;ctx&lt;/span&gt;)
    &lt;span class="pl-s1"&gt;userInfo&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;clientWithTimeout&lt;/span&gt;.&lt;span class="pl-en"&gt;GetUserInfo&lt;/span&gt;(&lt;span class="pl-s"&gt;"user-id"&lt;/span&gt;)

    &lt;span class="pl-c"&gt;// Attach a logger&lt;/span&gt;
    &lt;span class="pl-s1"&gt;logClient&lt;/span&gt;, &lt;span class="pl-s1"&gt;err&lt;/span&gt; &lt;span class="pl-c1"&gt;:=&lt;/span&gt; &lt;span class="pl-s1"&gt;logging&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/isovalent/instruqt-go" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;One of the issues in publishing Instruqt labs is to link user information from Instruqt with that of your own database or CRM.&lt;br&gt;
In this first post, we’ll guide you through building a proxy using &lt;code&gt;instruqt-go&lt;/code&gt; that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;collects user identifiers (e.g., HubSpot tokens);&lt;/li&gt;
&lt;li&gt;validates user identity;&lt;/li&gt;
&lt;li&gt;redirects users to a lab with unique access tokens generated via the Instruqt API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will then publish the function on Google Cloud Functions.&lt;/p&gt;
&lt;h1&gt;
  
  
  Why a Proxy
&lt;/h1&gt;

&lt;p&gt;There are various reasons to collect user information in labs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is useful to be able to generate badges (and &lt;a href="https://www.credly.com/organizations/isovalent/badges" rel="noopener noreferrer"&gt;we love badges&lt;/a&gt;) upon completion of the lab (more to come on that in a future post).&lt;/li&gt;
&lt;li&gt;It allows to show users their progress through the labs so they know which ones to take (see for example the &lt;a href="https://labs-map.isovalent.com/" rel="noopener noreferrer"&gt;Cilium Labs Map&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.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%2F5upiqyiiwbr6i5jv9lg8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5upiqyiiwbr6i5jv9lg8.png" alt="Cilium Labs Map" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  How to Pass User Data
&lt;/h1&gt;

&lt;p&gt;There are several methods to pass user data to Instruqt tracks.&lt;/p&gt;
&lt;h2&gt;
  
  
  Custom Parameters
&lt;/h2&gt;

&lt;p&gt;Instruqt &lt;a href="https://docs.instruqt.com/tracks/share/embed-a-track#custom-url-parameters" rel="noopener noreferrer"&gt;custom parameters&lt;/a&gt; are very useful to pass any kind of information when starting a track. These fields are simply added to the URL as query parameters, prefixed with &lt;code&gt;icp_&lt;/code&gt;. These parameters can also be retrieved in Instruqt webhooks as well as through the Instruqt GraphQL API, making them practical to use.&lt;/p&gt;

&lt;p&gt;Until recently, Instruqt encouraged track developers to pass user information (such as name, email, or token) using custom parameters.&lt;/p&gt;

&lt;p&gt;However, there are a few downsides to using custom parameters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They are not standardized, and Instruqt doesn't interpret them. This means user sessions will show as anonymous in Instruqt reports (and the unique user count might be wrong).&lt;/li&gt;
&lt;li&gt;They are not encrypted by default. You can of course encrypt them for your own keys, but Instruqt will show you the encrypted value in play reports.&lt;/li&gt;
&lt;li&gt;I have seen on multiple occasions custom parameters being lost when users restart a lab. I actually started my own cache database to counter this issue.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Invites
&lt;/h2&gt;

&lt;p&gt;Instruqt invites allow to create a list of tracks and generate an invitation link that can be shared with users for easy access. Invites can be set to collect user data via a form.&lt;/p&gt;

&lt;p&gt;This user data is then added to the user's details on Instruqt (user details are attached to user accounts, but are unique per Instruqt team).&lt;/p&gt;

&lt;p&gt;This is extremely practical for workshops, but there's again a few limitations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using an invite to access all labs means that invite must contain all published labs.&lt;/li&gt;
&lt;li&gt;Invites have their own landing page, so it wouldn't work with our &lt;a href="https://labs-map.isovalent.com/" rel="noopener noreferrer"&gt;Cilium Labs map&lt;/a&gt; or other kiosk approaches.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: Instruqt recently introduced landing pages, which is a form of invites with a way to tune the landing page, with the same advantages and limitations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Third Party Forms
&lt;/h2&gt;

&lt;p&gt;Recently, Instruqt added another way to pass user information, which combines the benefits of both previous methods.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.instruqt.com/tracks/share/embed-a-track#add-user-details-or-gate-access-with-a-third-party-form" rel="noopener noreferrer"&gt;encrypted PII method&lt;/a&gt; allows to pass a &lt;code&gt;pii_tpg&lt;/code&gt; query parameter to an embed URL. This means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The data is encrypted, using a public key provided by Instruqt, so URLs don't contain readable user information.&lt;/li&gt;
&lt;li&gt;Instruqt understands the &lt;code&gt;pii_tpg&lt;/code&gt; data and has the private key to decrypt it. The information is used to fill in the user's details, just as if they had acceted an invite.&lt;/li&gt;
&lt;li&gt;This is not linked to invites, so it can be used with any track.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We're going to use this new method in this example, as it is the most versatile today to pass information to Instruqt in a safe and reliable manner.&lt;/p&gt;
&lt;h1&gt;
  
  
  A Note on Embed Tokens
&lt;/h1&gt;

&lt;p&gt;When you visit a track page on Instruqt, there is an option to embed the track.&lt;br&gt;
This gives you a URL which contains a token unique to the track.&lt;/p&gt;

&lt;p&gt;While it is perfectly valid to use that URL, it also means that whoever has access to this token can start the track whenever they want.&lt;/p&gt;

&lt;p&gt;Instruqt has recently added an API call to generate one-time tokens for tracks, such that URLs using such tokens can only be used once.&lt;/p&gt;

&lt;p&gt;The proxy we're coding will use one-time tokens, since we have access to the API and can easily generate them.&lt;/p&gt;
&lt;h1&gt;
  
  
  Creating the Proxy
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Initial Steps
&lt;/h2&gt;

&lt;p&gt;First, create a directory for your function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;instruqt-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move to this directory and initialize the Go environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Replace example.com with the prefix of your choice&lt;/span&gt;
go mod init example.com/labs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Google Cloud Function Harnessing
&lt;/h2&gt;

&lt;p&gt;For local testing, create a &lt;code&gt;cmd&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;cmd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;main.go&lt;/code&gt; file in that directory, with the following content:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="c"&gt;// Blank-import the function package so the init() runs&lt;/span&gt;
  &lt;span class="c"&gt;// Adapt if you replaced example.com earlier&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"example.com/labs"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/GoogleCloudPlatform/functions-framework-go/funcframework"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Use PORT environment variable, or default to 8080.&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"8080"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;envPort&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;envPort&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;envPort&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;funcframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"funcframework.Start: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Function
&lt;/h2&gt;

&lt;p&gt;Back to the &lt;code&gt;instruqt-proxy&lt;/code&gt; directory, create a &lt;code&gt;proxy.go&lt;/code&gt; file and start by adding the &lt;code&gt;init()&lt;/code&gt; function to it, along with the Go packages we will be using:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"net/url"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/GoogleCloudPlatform/functions-framework-go/functions"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/isovalent/instruqt-go/instruqt"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;functions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"InstruqtProxy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruqtProxy&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 will allow Google Cloud Functions to call the &lt;code&gt;instruqtProxy&lt;/code&gt; function when it is initialized.&lt;/p&gt;

&lt;p&gt;Let's write that 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;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c"&gt;// Replace team name with yours&lt;/span&gt;
    &lt;span class="n"&gt;instruqtTeam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"isovalent"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;instruqtProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;instruqtToken&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSTRUQT_TOKEN"&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;instruqtToken&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;instruqtClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instruqtToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruqtTeam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Get user from passed token&lt;/span&gt;
    &lt;span class="n"&gt;utk&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"utk"&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;utk&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;labSlug&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getLabURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instruqtClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labSlug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusFound&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 function, we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;get the Instruqt token from the &lt;code&gt;INSTRUQT_TOKEN&lt;/code&gt; environment variable&lt;/li&gt;
&lt;li&gt;initialize the Instruqt API client for the token and team&lt;/li&gt;
&lt;li&gt;retrieve a &lt;code&gt;utk&lt;/code&gt; parameter from the URL parameters in order to authenticate the user&lt;/li&gt;
&lt;li&gt;get user information based on that UTK&lt;/li&gt;
&lt;li&gt;get the lab slug from the URL parameters&lt;/li&gt;
&lt;li&gt;retrieve the lab URL for the redirection&lt;/li&gt;
&lt;li&gt;redirect the user using an &lt;code&gt;http.Redirect&lt;/code&gt; function&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Implement getLabURL()
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;getLabURL&lt;/code&gt; function will generate the redirect URL for the lab based on user information, the requested lab slug, and dynamic information from the Instruqt API.&lt;/p&gt;

&lt;p&gt;Let's write it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c"&gt;// Replace with your sign-up page format&lt;/span&gt;
    &lt;span class="n"&gt;labSignupPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://isovalent.com/labs/%s"&lt;/span&gt;

    &lt;span class="c"&gt;// Adapt to your values&lt;/span&gt;
    &lt;span class="n"&gt;finishBtnText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Try your next Lab!"&lt;/span&gt;
    &lt;span class="n"&gt;finishBtnURL&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://labs-map.isovalent.com/map?lab=%s&amp;amp;showInfo=true"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getLabURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instruqtClient&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;instruqt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqtClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetTrackBySlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Unknown user&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;labSignupPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slug&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;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Get one-time token&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqtClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GenerateOneTimePlayToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;labURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://play.instruqt.com/embed/%s/tracks/%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruqtTeam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Prepare the fields to encrypt&lt;/span&gt;
    &lt;span class="n"&gt;encryptedPII&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;instruqtClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncryptUserPII&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Add params&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&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;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;             &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"pii_tpg"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="n"&gt;encryptedPII&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"show_challenges"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"finish_btn_target"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"_blank"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"finish_btn_text"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;finishBtnText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"finish_btn_url"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finishBtnURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;labURL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Encode the parameters&lt;/span&gt;
    &lt;span class="n"&gt;labURL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&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;labURL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, note that we have defined some new constants that you can tune:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;labSignupPage&lt;/code&gt; is the URL on your website where unauthenticated users will be redirected. It contains a variable for the lab slug.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;finishBtnText&lt;/code&gt; is the text shown on the finish button of the lab.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;finishBtnURL&lt;/code&gt; is the action of the button at the end of the lab. It also contains a variable for the lab slug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's explain the &lt;code&gt;getLabURL()&lt;/code&gt; function steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieve track information from the Instruqt API, error if it cannot be found.&lt;/li&gt;
&lt;li&gt;If the user is unknown, redirect to sign-up page.&lt;/li&gt;
&lt;li&gt;Generate a one-time token for the embedded track access.&lt;/li&gt;
&lt;li&gt;Generate the redirect URL.&lt;/li&gt;
&lt;li&gt;Encrypt user information using the PII key from the Instruqt API.&lt;/li&gt;
&lt;li&gt;Add all parameters (one-time token, encrypted user information, finish button options) to the redirect URL.&lt;/li&gt;
&lt;li&gt;Encode the URL.&lt;/li&gt;
&lt;li&gt;Return the resulting URL.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;getUser()&lt;/code&gt; Function
&lt;/h2&gt;

&lt;p&gt;The last piece missing in this proxy is the &lt;code&gt;getUser()&lt;/code&gt; function. I can't help you much here, since this part is where you plug your own logic. You might be using a CRM like Hubspot to &lt;a href="https://legacydocs.hubspot.com/docs/methods/contacts/get_contact_by_utk" rel="noopener noreferrer"&gt;retrieve contact information from the UTK&lt;/a&gt;, or another database, it's up to you!&lt;/p&gt;

&lt;p&gt;The code I'll show you here simply returns a sample user:&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;/*
 * This is where you add the logic to get user information from your CRM/database.
 */&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;LastName&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Email&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utk&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Implement the logic to get your user information from UTK&lt;/span&gt;

    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"john@doe.com"&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;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Testing the code
&lt;/h1&gt;

&lt;p&gt;Now that we have our whole &lt;code&gt;proxy.go&lt;/code&gt; function, let's test it!&lt;/p&gt;

&lt;p&gt;First, update your &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; files with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get ./...
go mod tidy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your Instruqt dashboard, go to "API keys" and get the value of your API key. Export it as a variable in your shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;INSTRUQT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your_instruqt_token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, launch the function on your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FUNCTION_TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;InstruqtProxy go run ./cmd/main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, in another terminal, make test requests to &lt;code&gt;localhost:8080&lt;/code&gt; where your function will be running (you can pass a &lt;code&gt;PORT&lt;/code&gt; environment variable above to change the port if necessary):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"localhost:8080/?slug=cilium-getting-started&amp;amp;utk=someUtkValue"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adapt to use a track slug that exists in your Instruqt organization. If the track exists, you should get a &lt;code&gt;302&lt;/code&gt; response with the redirect URL containing a one-time token for access, as well as John Doe's information encrypted with the PII key, and a one-time token (starting with &lt;code&gt;ott_&lt;/code&gt;)!&lt;/p&gt;

&lt;h1&gt;
  
  
  Alternative testing: using Docker
&lt;/h1&gt;

&lt;p&gt;If you'd like to use Docker to test your function locally, you can create a &lt;code&gt;Dockerfile&lt;/code&gt; in your current directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.23&lt;/span&gt;

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

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; myapp ./cmd/main.go

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; DEV=true&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=8080&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; $PORT&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["./myapp"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;docker-compose.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;INSTRUQT_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${INSTRUQT_TOKEN}&lt;/span&gt;
      &lt;span class="na"&gt;FUNCTION_TARGET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InstruqtProxy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, build and launch your container:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;And you can send requests to &lt;code&gt;localhost:8080&lt;/code&gt; just the same as before!&lt;/p&gt;

&lt;h1&gt;
  
  
  Hosting the Proxy on Google Cloud Functions
&lt;/h1&gt;

&lt;p&gt;In order to deploy to Google Cloud, first make sure you are logged in to your Google Cloud project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud auth application-default login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a Secret for the API Token
&lt;/h2&gt;

&lt;p&gt;Next, let's create a new secret for the Instruqt token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTRUQT_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | gcloud secrets create instruqt-token &lt;span class="nt"&gt;--data-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to adjust the permissions on this secret, you will need to get your project ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PROJECT_NUMBER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud projects describe &lt;span class="si"&gt;$(&lt;/span&gt;gcloud config get-value project&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"value(projectNumber)"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, add the "Secret Manager Secret Accessor" role for the default Compute Engine service account for the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud secrets add-iam-policy-binding instruqt-token &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--member&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"serviceAccount:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NUMBER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-compute@developer.gserviceaccount.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"roles/secretmanager.secretAccessor"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your secret is now ready to be used by the function!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the Function
&lt;/h2&gt;

&lt;p&gt;You can then deploy the function (adapt the region if needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud functions deploy &lt;span class="s2"&gt;"instruqt-proxy"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gen2&lt;/span&gt; &lt;span class="nt"&gt;--runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;go122 &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;europe-west1 &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--entry-point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"InstruqtProxy"&lt;/span&gt; &lt;span class="nt"&gt;--trigger-http&lt;/span&gt; &lt;span class="nt"&gt;--allow-unauthenticated&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-secrets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"INSTRUQT_TOKEN=instruqt-token:latest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will upload and build your project, and return the URL to access the function.&lt;/p&gt;

&lt;p&gt;This URL will look something like &lt;code&gt;https://europe-west1-&amp;lt;project&amp;gt;.cloudfunctions.net/instruqt-proxy&lt;/code&gt;.&lt;br&gt;
You can then test the function using that URL instead of &lt;code&gt;localhost:8080&lt;/code&gt;!&lt;/p&gt;

&lt;h1&gt;
  
  
  Further Considerations
&lt;/h1&gt;

&lt;p&gt;This is a simplified approach to the lab proxy we use at Isovalent. There are things you might want to consider with this implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you have actual user (instead of marketing contact), switch to a proper authentication system (e.g. JWT) instead of UTK.&lt;/li&gt;
&lt;li&gt;The current implementation will give access to any lab in your collection if you know its slug. You might want to filter them out (using for example track tags).&lt;/li&gt;
&lt;li&gt;This implementation manages errors but is very basic in logging. We would recommend using Google Cloud logging to easily audit function runs.&lt;/li&gt;
&lt;li&gt;You might want to pass extra information as &lt;a href="https://docs.instruqt.com/tracks/share/embed-a-track#custom-url-parameters" rel="noopener noreferrer"&gt;custom parameters&lt;/a&gt;. For example, we like to pass some form of event or campaign ID. These can then be retrieved via the API as part or the Track structure.&lt;/li&gt;
&lt;li&gt;If you're using a custom form and redirecting to the proxy, you might want to be sure your CRM/database has already gotten the user information. You can for example implement a retry logic in the proxy function.&lt;/li&gt;
&lt;li&gt;Invite embed URLs contain the invite ID. If you want to support invites, the proxy could take an optional &lt;code&gt;invite&lt;/code&gt; parameter and add it to the URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Using the Proxy
&lt;/h1&gt;

&lt;p&gt;This proxy can typically be used to give access to authenticated users in a safe way, while preserving user information in Instruqt reports and making sure embed URLs are not reusable.&lt;/p&gt;

&lt;p&gt;Here is an example of usage of this proxy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up lab sign-up pages with the form system of your choice (e.g. using Hubspot forms).&lt;/li&gt;
&lt;li&gt;Retrieve a user identifier from the page context (e.g. a &lt;a href="https://knowledge.hubspot.com/privacy-and-consent/what-cookies-does-hubspot-set-in-a-visitor-s-browser" rel="noopener noreferrer"&gt;Hubspot cookie token&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Redirect users to the proxy, passing the user identifier and lab slug as parameters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This can allow you to build a series of public sign-up pages for your labs, similar to what we have built on &lt;a href="https://isovalent.com/labs/" rel="noopener noreferrer"&gt;the Isovalent website&lt;/a&gt;. It can also be used to build a &lt;a href="https://www.youtube.com/watch?v=QEh16xu1xvc&amp;amp;t=3s" rel="noopener noreferrer"&gt;Kiosk interface&lt;/a&gt;, or even a more creative landing page such as the &lt;a href="https://labs-map.isovalent.com/" rel="noopener noreferrer"&gt;Cilium Labs map&lt;/a&gt;, where clicks on the map redirect to the lab proxy.&lt;/p&gt;

&lt;p&gt;By making a complex networking technology like Cilium fun with our labs, we have made it the experience for users less intimidating and more approachable. Using our proxy can help you provide a similar user experience to your prospects. Please get in touch if you have any questions.&lt;/p&gt;

</description>
      <category>instruqt</category>
      <category>labs</category>
      <category>go</category>
      <category>devrel</category>
    </item>
    <item>
      <title>Towards a Modular DevOps Stack</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Wed, 23 Feb 2022 17:37:45 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/towards-a-modular-devops-stack-257c</link>
      <guid>https://dev.to/camptocamp-ops/towards-a-modular-devops-stack-257c</guid>
      <description>&lt;p&gt;A year and a half ago, our infrastructure team at Camptocamp was faced with an increasingly problematic situation. We were provisioning more and more Kubernetes clusters, on different cloud providers. We used Terraform to deploy the infrastructure itself, and we had started to adopt &lt;a href="https://argoproj.github.io/cd/" rel="noopener noreferrer"&gt;Argo CD&lt;/a&gt; to deploy applications on top of the cluster.&lt;/p&gt;

&lt;p&gt;We quickly ended up with many projects using similar logic, often borrowed from older projects, and most of these cluster were starting to use divergent code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvl7hboahfscgsbvfyit4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvl7hboahfscgsbvfyit4.png" alt="Diverging projects" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We thought it was time to put together a standard core in order to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provision Kubernetes clusters;&lt;/li&gt;
&lt;li&gt;deploy standard applications sets (monitoring, ingress controller, certificate management, etc.) on them;&lt;/li&gt;
&lt;li&gt;provide an interface for developers to deploy their applications in a GitOps manner;&lt;/li&gt;
&lt;li&gt;ensure all teams used similar approaches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhl1ncdyg1o7bolr9vy5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhl1ncdyg1o7bolr9vy5z.png" alt="DevOps Stack" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://devops-stack.io/" rel="noopener noreferrer"&gt;DevOps Stack&lt;/a&gt; was born!&lt;/p&gt;

&lt;h1&gt;
  
  
  🌟 The Original Design
&lt;/h1&gt;

&lt;p&gt;The original DevOps Stack design was monolithic, both for practical and technical reasons.&lt;/p&gt;

&lt;p&gt;After all, we were trying to centralize best practices from many projects into a common core, so it made sense to put everything together behind a unified interface!&lt;/p&gt;

&lt;p&gt;In addition to that, all the Kubernetes applications were created using an &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/#app-of-apps-pattern" rel="noopener noreferrer"&gt;App of Apps pattern&lt;/a&gt;, because we had no ApplicationSets and no way to control Argo CD directly from Terraform.&lt;/p&gt;

&lt;p&gt;As a result, the basic interface was very simple, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"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::https://github.com/camptocamp/devops-stack.git//modules/eks/aws?ref=v0.54.0"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&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;worker_groups&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="nx"&gt;instance_type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"m5a.large"&lt;/span&gt;
      &lt;span class="nx"&gt;asg_desired_capacity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="nx"&gt;asg_max_size&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;base_domain&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;cognito_user_pool_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cognito_user_pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pool&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;cognito_user_pool_domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cognito_user_pool_domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pool_domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it could get very complex when application settings needed to be tuned!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12b9atbhi9iwf0fgoslm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12b9atbhi9iwf0fgoslm.png" alt="DevOps Stack v0 Architecture" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With time, this architecture started being problematic for various reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it was hard to deactivate or replace components (monitoring, ingress controller, etc.), making it hard to extend the core features;&lt;/li&gt;
&lt;li&gt;default YAML values got quite mixed up and hard to understand;&lt;/li&gt;
&lt;li&gt;as a result, overriding default values was unnecessarily complex;&lt;/li&gt;
&lt;li&gt;adding new applications was done using &lt;code&gt;extra_apps&lt;/code&gt; and &lt;code&gt;extra_applicationsets&lt;/code&gt; parameters, which were monolithic and complex.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was time to rethink the design.&lt;/p&gt;

&lt;h1&gt;
  
  
  🐙 Argo CD Provider
&lt;/h1&gt;

&lt;p&gt;In order to escape the App of Apps pattern, we started looking into using a &lt;a href="https://registry.terraform.io/providers/oboukili/argocd/" rel="noopener noreferrer"&gt;Terraform provider to control Argo CD resources&lt;/a&gt;. After various contributions, the provider was ready for us to start using in the DevOps Stack.&lt;/p&gt;

&lt;h1&gt;
  
  
  🗺 The Plan for Modularization
&lt;/h1&gt;

&lt;p&gt;Using the Argo CD provider allowed us to split each component into a separate module. In a similar way to DevOps Stack v0, each of these modules would provide Terraform code to set up the component, optionally with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cloud resources required to set up the component;&lt;/li&gt;
&lt;li&gt;Helm charts to deploy the application using Argo CD.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 A Full Example
&lt;/h2&gt;

&lt;p&gt;As a result, the user interface is much more verbose. The previous example would thus become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"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::https://github.com/camptocamp/devops-stack.git//modules/eks/aws?ref=v1.0.0"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;base_domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"demo.camptocamp.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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&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;cluster_endpoint_public_access_cidrs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;formatlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%s/32"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nat_public_ips&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="nx"&gt;worker_groups&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="nx"&gt;instance_type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"m5a.large"&lt;/span&gt;
      &lt;span class="nx"&gt;asg_desired_capacity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="nx"&gt;asg_max_size&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
      &lt;span class="nx"&gt;root_volume_type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gp2"&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="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"argocd"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_addr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1:8080"&lt;/span&gt;
  &lt;span class="nx"&gt;auth_token&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_auth_token&lt;/span&gt;
  &lt;span class="nx"&gt;insecure&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;plain_text&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;port_forward&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;port_forward_with_namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_namespace&lt;/span&gt;

  &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubernetes_host&lt;/span&gt;
    &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubernetes_cluster_ca_certificate&lt;/span&gt;
    &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubernetes_token&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ingress"&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::https://github.com/camptocamp/devops-stack-module-traefik.git//modules/eks"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;argocd_namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_namespace&lt;/span&gt;
  &lt;span class="nx"&gt;base_domain&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base_domain&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"oidc"&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::https://github.com/camptocamp/devops-stack-module-oidc-aws-cognito.git//modules"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;argocd_namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_namespace&lt;/span&gt;
  &lt;span class="nx"&gt;base_domain&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base_domain&lt;/span&gt;

  &lt;span class="nx"&gt;cognito_user_pool_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cognito_user_pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pool&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;cognito_user_pool_domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cognito_user_pool_domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pool_domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"monitoring"&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::https://github.com/camptocamp/devops-stack-module-kube-prometheus-stack.git//modules"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;oidc&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc&lt;/span&gt;
  &lt;span class="nx"&gt;argocd_namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_namespace&lt;/span&gt;
  &lt;span class="nx"&gt;base_domain&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base_domain&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_issuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"letsencrypt-prod"&lt;/span&gt;
  &lt;span class="nx"&gt;metrics_archives&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"loki-stack"&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::https://github.com/camptocamp/devops-stack-module-loki-stack.git//modules/eks"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;argocd_namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_namespace&lt;/span&gt;
  &lt;span class="nx"&gt;base_domain&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base_domain&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_oidc_issuer_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_oidc_issuer_url&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;monitoring&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"cert-manager"&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::https://github.com/camptocamp/devops-stack-module-cert-manager.git//modules/eks"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;argocd_namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_namespace&lt;/span&gt;
  &lt;span class="nx"&gt;base_domain&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base_domain&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_oidc_issuer_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_oidc_issuer_url&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;monitoring&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"argocd"&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::https://github.com/camptocamp/devops-stack-module-argocd.git//modules"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;oidc&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc&lt;/span&gt;
  &lt;span class="nx"&gt;argocd&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_namespace&lt;/span&gt;
    &lt;span class="nx"&gt;server_secrhttps&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//kubernetes.slack.com/archives/C01SQ1TMBSTetkey = module.cluster.argocd_server_secretkey&lt;/span&gt;
    &lt;span class="nx"&gt;accounts_pipeline_tokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_accounts_pipeline_tokens&lt;/span&gt;
    &lt;span class="nx"&gt;server_admin_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_server_admin_password&lt;/span&gt;
    &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_domain&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;base_domain&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base_domain&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_issuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"letsencrypt-prod"&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert-manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;monitoring&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ☸ The Cluster Module
&lt;/h3&gt;

&lt;p&gt;As you can see, the main &lt;code&gt;cluster&lt;/code&gt; module —which used to do all the work— is now only responsible for deploying the Kubernetes cluster and a basic Argo CD set up (in bootstrap mode).&lt;/p&gt;

&lt;h3&gt;
  
  
  🐙 The Argo CD Provider
&lt;/h3&gt;

&lt;p&gt;We then set up the Argo CD provider using outputs from the &lt;code&gt;cluster&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"argocd"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_addr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1:8080"&lt;/span&gt;
  &lt;span class="nx"&gt;auth_token&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_auth_token&lt;/span&gt;
  &lt;span class="nx"&gt;insecure&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;plain_text&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;port_forward&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;port_forward_with_namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_namespace&lt;/span&gt;

  &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubernetes_host&lt;/span&gt;
    &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubernetes_cluster_ca_certificate&lt;/span&gt;
    &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubernetes_token&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 provider is used in all (or most at least) other modules in order to deploy Argo CD applications, without going through a monolithic/centralized App of Apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧩 Component Modules
&lt;/h3&gt;

&lt;p&gt;Each module provides a single component, using Terraform and the Argo CD provider (optionally).&lt;/p&gt;

&lt;p&gt;There are common interfaces passed as variables between modules. For example, the &lt;code&gt;oidc&lt;/code&gt; variable can be provided as an output from various modules: &lt;a href="https://github.com/camptocamp/devops-stack-module-keycloak" rel="noopener noreferrer"&gt;Keycloak&lt;/a&gt;, &lt;a href="https://github.com/camptocamp/devops-stack-module-oidc-aws-cognito" rel="noopener noreferrer"&gt;AWS Cognito&lt;/a&gt;, etc. This &lt;code&gt;oidc&lt;/code&gt; variable can then be passed to other component modules to configure the component's authentication layer.&lt;/p&gt;

&lt;p&gt;In a similar fashion, the &lt;code&gt;ingress&lt;/code&gt; module, which was so far only using Traefik, can now be replaced by another Ingress Controller implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  🐙 The Argo CD Module
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/camptocamp/devops-stack-module-argocd" rel="noopener noreferrer"&gt;Argo CD module&lt;/a&gt; is a special one. A special entrypoint (called &lt;code&gt;boostrap&lt;/code&gt;) is used in the &lt;code&gt;cluster&lt;/code&gt; module to set up a basic Argo CD using Helm.&lt;/p&gt;

&lt;p&gt;The user is then responsible for instantiating the Argo CD module a second time at the end of the stack, in order for Argo CD to manage itself and configure itself properly —including monitoring and authentication, which cannot be configured before the corresponding components are deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  🥾 The Bootstrap Pattern
&lt;/h2&gt;

&lt;p&gt;Eventually, other modules might adopt the bootstrap pattern in order to solve chicken-and-egg dependencies.&lt;/p&gt;

&lt;p&gt;For example, &lt;a href="https://github.com/camptocamp/devops-stack-module-cert-manager" rel="noopener noreferrer"&gt;cert-manager&lt;/a&gt; requires Prometheus Operator CRDs in order to be monitored. However, the &lt;a href="https://github.com/camptocamp/devops-stack-module-kube-prometheus-stack" rel="noopener noreferrer"&gt;Kube Prometheus Stack&lt;/a&gt; might require valid certificates, thus depending on cert-manager being deployed. This could be solved by deploying a basic cert-manager instance (in a &lt;code&gt;bootstrap&lt;/code&gt; endpoint), and finalizing the deploying at the end of the stack.&lt;/p&gt;

&lt;h1&gt;
  
  
  🚀 To Infinity
&lt;/h1&gt;

&lt;p&gt;The modular DevOps Stack design is the current target for release 1.0.0.&lt;/p&gt;

&lt;p&gt;While this refactoring is still in beta state, it can be tested by using the &lt;a href="https://github.com/camptocamp/devops-stack/tree/v1" rel="noopener noreferrer"&gt;&lt;code&gt;v1&lt;/code&gt; branch&lt;/a&gt; of the project. You can also find examples in the &lt;a href="https://github.com/camptocamp/devops-stack/tree/v1/tests" rel="noopener noreferrer"&gt;&lt;code&gt;tests&lt;/code&gt; directory&lt;/a&gt; (though not all distributions are ported yet).&lt;/p&gt;

&lt;p&gt;Feedback is welcome, and you can contact us on the &lt;a href="https://kubernetes.slack.com/archives/C01SQ1TMBST" rel="noopener noreferrer"&gt;&lt;code&gt;#camptocamp-devops-stack&lt;/code&gt; channel&lt;/a&gt; of the Kubernetes Slack.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>argocd</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>A 15-year Puppet Journey</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Sun, 06 Feb 2022 22:59:02 +0000</pubDate>
      <link>https://dev.to/raphink/a-15-year-puppet-journey-4o39</link>
      <guid>https://dev.to/raphink/a-15-year-puppet-journey-4o39</guid>
      <description>&lt;p&gt;&lt;em&gt;Note: this is a personal blog post. It does not concern Camptocamp's partnership with Puppet Inc., which remains unchanged.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In 2006, I landed my first gig as a Systems Administrator. One of my main roles was taking care of a Cfengine server and repository for about 4000 machines, and finishing its migration from Cfengine 1 to Cfengine 2.&lt;/p&gt;

&lt;p&gt;Like many people in this position at that time, discovering Puppet was akin to a revelation. Leaving aside its slowness (in comparison to Cfengine), its DSL, file templates, and the extensibility of the Resource Abstraction Layer were nothing short of a little revolution.&lt;/p&gt;

&lt;p&gt;Then Augeas was presented to me, and I enjoyed the concept so much I got involved in the project and started writing many lenses.&lt;/p&gt;

&lt;p&gt;I joined Camptocamp in 2012 in large part because of the role the company played in the Puppet community. Together with my colleague &lt;a href="https://github.com/mcanevet" rel="noopener noreferrer"&gt;Mickaël&lt;/a&gt;, we took to standardising and modernising our Puppet stack and modules, and got deeply involved in the community, writing plugins (puppet-lint plugins, &lt;a href="https://github.com/voxpupuli/facterdb" rel="noopener noreferrer"&gt;facterdb&lt;/a&gt;/rspec-puppet-facter) and tools (&lt;a href="https://www.camptocamp.com/fr/actualites-evenements/news_integrating_prometheus" rel="noopener noreferrer"&gt;prometheus-puppetdb-sd&lt;/a&gt;, &lt;a href="https://github.com/camptocamp/puppetfile-updater" rel="noopener noreferrer"&gt;puppetfile-updater&lt;/a&gt;, &lt;a href="https://www.camptocamp.com/fr/actualites-evenements/news_cleaning_up_puppet_code" rel="noopener noreferrer"&gt;puppet-ghostbuster&lt;/a&gt;, etc.).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bgtu2pv4ftg5suj31rv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bgtu2pv4ftg5suj31rv.jpg" alt="Puppet Community" width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Over the years, I've had the pleasure of teaching Puppet training courses at all levels and consulting on all sorts of layers (modules, Ruby plugins, TDD, etc.) and stacks (Puppet Enterprise, Foreman, Docker-based, and more…).&lt;/p&gt;

&lt;p&gt;In the last couple of years though, my work has mostly revolved around containers and Kubernetes. I have kept maintaining Puppet- and Augeas-related code, but often without using these projects myself for production needs.&lt;/p&gt;

&lt;p&gt;For that reason, I started donating such projects to &lt;a href="https://voxpupuli.org" rel="noopener noreferrer"&gt;Voxpupuli&lt;/a&gt;, as I believe they will receive better care than I can currently give them.&lt;/p&gt;

&lt;p&gt;Truth be told, I've delayed all this for months —maybe years— because the Puppet community is awesome and it's always been a pleasure to contribute to it. I've even gotten back quite a bit last year, reviving &lt;a href="https://forge.puppet.com/modules/camptocamp/catalog_diff" rel="noopener noreferrer"&gt;puppet-catalog-diff&lt;/a&gt; and participating in various Puppet Camps.&lt;/p&gt;

&lt;p&gt;It's been fun, but I need to focus on other projects now, and it's probably better if things are set in a clear manner.&lt;/p&gt;

&lt;p&gt;So farewell Puppet community, keep being an awesome and welcoming place, &amp;amp; thanks for all the 🐟!&lt;/p&gt;

</description>
      <category>puppet</category>
      <category>cfgmgmt</category>
      <category>devops</category>
      <category>community</category>
    </item>
    <item>
      <title>How to allow dynamic Terraform Provider Configuration</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Tue, 11 May 2021 11:47:57 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/how-to-allow-dynamic-terraform-provider-configuration-20ik</link>
      <guid>https://dev.to/camptocamp-ops/how-to-allow-dynamic-terraform-provider-configuration-20ik</guid>
      <description>&lt;p&gt;&lt;a href="http://terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; relies heavily on the concept of &lt;a href="https://www.terraform.io/docs/providers/index.html" rel="noopener noreferrer"&gt;providers&lt;/a&gt;, a base brick which consists of Go plugins enabling the communication with an API.&lt;/p&gt;

&lt;p&gt;Each provider gives access to one or more resource types, and these resources then manage objects on the target API.&lt;/p&gt;

&lt;p&gt;Most of the time, a provider's configuration is static, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&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;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in some cases, it is useful to configure a provider dynamically, using the attribute values from other resources as input for the provider's configuration.&lt;/p&gt;

&lt;p&gt;I'll use the example of the &lt;a href="https://github.com/oboukili/terraform-provider-argocd" rel="noopener noreferrer"&gt;Argo CD provider&lt;/a&gt;. &lt;em&gt;In a single Terraform run&lt;/em&gt;, we would like to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install a Kubernetes cluster (using a &lt;a href="https://devops-stack.io" rel="noopener noreferrer"&gt;DevOps Stack&lt;/a&gt; K3s Terraform module)&lt;/li&gt;
&lt;li&gt;install Argo CD on the the cluster using the &lt;a href="https://registry.terraform.io/providers/hashicorp/helm/latest/docs" rel="noopener noreferrer"&gt;Helm provider&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;instantiate Argo CD resources (projects, applications, etc.) on this new Argo CD server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our code will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Install Kubernetes &amp;amp; Argo CD using a local module&lt;/span&gt;
&lt;span class="c1"&gt;# (from https://devops-stack.io)&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"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::https://github.com/camptocamp/devops-stack.git//modules/k3s/docker?ref=master"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt;
  &lt;span class="nx"&gt;node_count&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# /!\ Setup the Argo CD provider dynamically&lt;/span&gt;
&lt;span class="c1"&gt;# based on the cluster module's output&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"argocd"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_addr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_server&lt;/span&gt;
  &lt;span class="nx"&gt;auth_token&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argocd_auth_token&lt;/span&gt;
  &lt;span class="nx"&gt;insecure&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;grpc_web&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;span class="c1"&gt;# Deploy an Argo CD resource using the provider&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"argocd_project"&lt;/span&gt; &lt;span class="s2"&gt;"demo_app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&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;"demo-app"&lt;/span&gt;
    &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"argocd"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Demo application project"&lt;/span&gt;
    &lt;span class="nx"&gt;source_repos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;server&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://kubernetes.default.svc"&lt;/span&gt;
      &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;orphaned_resources&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;warn&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;span class="p"&gt;}&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&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 requires to configure Argo CD dynamically, using the output of the Kubernetes cluster's resources.&lt;/p&gt;

&lt;h1&gt;
  
  
  Provider Initialization
&lt;/h1&gt;

&lt;p&gt;Providers are initialized early in a Terraform run, as their initialization is required to compute the graph which defines in which order the resources are applied.&lt;/p&gt;

&lt;p&gt;This means it is actually not possible to make a provider initialize after a secondary resource is created.&lt;/p&gt;

&lt;p&gt;Officially, the story stops here, and Terraform has &lt;a href="https://github.com/hashicorp/terraform/issues/24055" rel="noopener noreferrer"&gt;a bug report&lt;/a&gt; to track the feature allowing to dynamically configure providers.&lt;/p&gt;

&lt;p&gt;So… it's game over then? 🎮 👾&lt;br&gt;
Not really!&lt;/p&gt;
&lt;h1&gt;
  
  
  Leveraging Pointers
&lt;/h1&gt;

&lt;p&gt;When a provider is configured in Terraform, it triggers a configuration 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;Provider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;ConfigureFunc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Create someObject&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;someObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;ConfigureFunc&lt;/code&gt; method is usually used to create a static client for the target API. In the Argo CD provider for example, it returns a &lt;code&gt;ServerInterface&lt;/code&gt; structure, with pointers to several clients, instantiated from the provider parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ServerInterface&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                   
    &lt;span class="n"&gt;ApiClient&lt;/span&gt;            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;apiclient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;                                      
    &lt;span class="n"&gt;ApplicationClient&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplicationServiceClient&lt;/span&gt;                  
    &lt;span class="n"&gt;ClusterClient&lt;/span&gt;        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClusterServiceClient&lt;/span&gt;                          
    &lt;span class="n"&gt;ProjectClient&lt;/span&gt;        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProjectServiceClient&lt;/span&gt;                          
    &lt;span class="n"&gt;RepositoryClient&lt;/span&gt;     &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RepositoryServiceClient&lt;/span&gt;                    
    &lt;span class="n"&gt;RepoCredsClient&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;repocreds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RepoCredsServiceClient&lt;/span&gt;                      
    &lt;span class="n"&gt;ServerVersion&lt;/span&gt;        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;semver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;                                        
    &lt;span class="n"&gt;ServerVersionMessage&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VersionMessage&lt;/span&gt;                                                                                                              
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The return statement from the &lt;code&gt;ConfigureFunc&lt;/code&gt; eventually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ServerInterface&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;                                                             
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                     
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;applicationClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                             
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;clusterClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                 
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                 
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;repositoryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                              
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;repoCredsClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                               
    &lt;span class="n"&gt;serverVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                                  
    &lt;span class="n"&gt;serverVersionMessage&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add a new field to the &lt;code&gt;ServerInterface&lt;/code&gt; to store the pointer to the provider's &lt;code&gt;ResourceData&lt;/code&gt; object, which gives access to the provider's parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ServerInterface&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                       
    &lt;span class="n"&gt;ApiClient&lt;/span&gt;            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;apiclient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;                                          
    &lt;span class="n"&gt;ApplicationClient&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplicationServiceClient&lt;/span&gt;                      
    &lt;span class="n"&gt;ClusterClient&lt;/span&gt;        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClusterServiceClient&lt;/span&gt;                              
    &lt;span class="n"&gt;ProjectClient&lt;/span&gt;        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProjectServiceClient&lt;/span&gt;                              
    &lt;span class="n"&gt;RepositoryClient&lt;/span&gt;     &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RepositoryServiceClient&lt;/span&gt;                        
    &lt;span class="n"&gt;RepoCredsClient&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;repocreds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RepoCredsServiceClient&lt;/span&gt;                          
    &lt;span class="n"&gt;ServerVersion&lt;/span&gt;        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;semver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;                                            
    &lt;span class="n"&gt;ServerVersionMessage&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VersionMessage&lt;/span&gt;                                    
    &lt;span class="n"&gt;ProviderData&lt;/span&gt;         &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceData&lt;/span&gt;                                       
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in the &lt;code&gt;ConfigureFunc&lt;/code&gt;, we'll instantiate the &lt;code&gt;ServerInterface&lt;/code&gt;, providing only the &lt;code&gt;ProviderData&lt;/code&gt; pointer. The first resource that needs to use the provider will then instantiate the clients, when the provider parameters are available. We'll need the &lt;code&gt;ConfigureFunc&lt;/code&gt; method to return a pointer to a &lt;code&gt;ServerInterface&lt;/code&gt;, so we can later cache the clients and avoid recreating them for every resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ConfigureFunc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                  
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ServerInterface&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ProviderData&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;                                      
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;                                                             
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Initialize the Clients
&lt;/h1&gt;

&lt;p&gt;Now we need to actually initialize the clients in each resource.&lt;/p&gt;

&lt;p&gt;Each resource method gets the interface returned by the &lt;code&gt;ConfigureFunc&lt;/code&gt; function as an empty interface parameter, usually called &lt;code&gt;meta&lt;/code&gt;:&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;resourceArgoCDProjectCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These methods currently simply cast the &lt;code&gt;meta&lt;/code&gt; parameter as a &lt;code&gt;ServerInterface&lt;/code&gt; structure and use the pre-initialized clients:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServerInterface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now need to cast &lt;code&gt;meta&lt;/code&gt; as a pointer to a &lt;code&gt;ServerInterface&lt;/code&gt; structure instead (since we'll need to modify the clients from within the resources), and initialize the clients:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ServerInterface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                                   
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initClients&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                        
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;                                                       
        &lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;                                                            
            &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                   
            &lt;span class="n"&gt;Summary&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to init clients"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                        
            &lt;span class="n"&gt;Detail&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;                                                  
        &lt;span class="p"&gt;},&lt;/span&gt;                                                                          
    &lt;span class="p"&gt;}&lt;/span&gt;                                                                           
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;initClients()&lt;/code&gt; method of the &lt;code&gt;ServerInterface&lt;/code&gt; structure will be called, allowing to set up the clients using the current provider parameters.&lt;/p&gt;

&lt;h1&gt;
  
  
  Client Pool Caching
&lt;/h1&gt;

&lt;p&gt;In the &lt;code&gt;ServerInterface#initClients()&lt;/code&gt; method, we want to make sure we reuse existing clients. This is rather simple, since each client is stored as a pointer in the structure, so it defaults to &lt;code&gt;nil&lt;/code&gt;:&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ServerInterface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;initClients&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                 
    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProviderData&lt;/span&gt;                                                         

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiClient&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                     
        &lt;span class="n"&gt;apiClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;initApiClient&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                                         
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;                                                          
        &lt;span class="p"&gt;}&lt;/span&gt;                                                                       
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiClient&lt;/span&gt;                                                
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// etc for all clients&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;That's it, we're done. With these modifications, &lt;code&gt;terraform plan&lt;/code&gt; now works. The resources get applied in the proper order, and the outputs from the &lt;code&gt;cluster&lt;/code&gt; module get properly passed as configuration to the Argo CD clients.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>go</category>
      <category>cfgmgmt</category>
    </item>
    <item>
      <title>March Cloud Native Romandie Meetup</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 01 Apr 2021 13:44:49 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/march-cloud-native-romandie-meetup-o2f</link>
      <guid>https://dev.to/camptocamp-ops/march-cloud-native-romandie-meetup-o2f</guid>
      <description>&lt;p&gt;Last week, we organized our last Cloud Native Romandie Meetup. Due to the current situation, this was an online event like the previous occurrences.&lt;/p&gt;

&lt;p&gt;The meetup was recorded and can be viewed again on YouTube.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/21DZ6hD97kM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Subjects
&lt;/h1&gt;

&lt;p&gt;For this edition, we had presentations by &lt;a href="https://www.cloudbees.com/" rel="noopener noreferrer"&gt;CloudBees&lt;/a&gt;, &lt;a href="https://exoscale.com" rel="noopener noreferrer"&gt;Exoscale&lt;/a&gt;, and &lt;a href="https://camptocamp.com" rel="noopener noreferrer"&gt;Camptocamp&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  CloudBees CI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/fredericgibelin/" rel="noopener noreferrer"&gt;Frédéric Gibelin&lt;/a&gt; from &lt;a href="https://www.cloudbees.com/" rel="noopener noreferrer"&gt;CloudBees&lt;/a&gt; presented &lt;a href="https://docs.cloudbees.com/docs/cloudbees-ci/latest/" rel="noopener noreferrer"&gt;CloudBees CI&lt;/a&gt;, a solution that helps users scale their Jenkins Enterprise platform in the Cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://drive.google.com/file/d/1u_pYAMA562a7Rzs2B-4XBImn6WsGHLpn/view" rel="noopener noreferrer"&gt;See the slides&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Exoscale SKS
&lt;/h2&gt;

&lt;p&gt;Next &lt;a href="https://twitter.com/pyr" rel="noopener noreferrer"&gt;Pierre-Yves Ritschard&lt;/a&gt; and &lt;a href="https://twitter.com/_mcorbin" rel="noopener noreferrer"&gt;Mathieu Corbin&lt;/a&gt; from &lt;a href="https://exoscale.com" rel="noopener noreferrer"&gt;Exoscale&lt;/a&gt; presented &lt;a href="https://community.exoscale.com/documentation/sks/" rel="noopener noreferrer"&gt;SKS&lt;/a&gt;, a Kubernetes as-a-Service, operating the user’s cluster on their behalf and taking care of the underlying infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="speakerdeck-iframe ltag_speakerdeck" src="https://speakerdeck.com/player/080c0fc92a8a4d37b5d4ef02eb590d19"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Camptocamp DevOps Stack
&lt;/h2&gt;

&lt;p&gt;To finish, &lt;a href="https://twitter.com/raphink" rel="noopener noreferrer"&gt;Raphaël Pinson&lt;/a&gt; presented Camptocamp's &lt;a href="https://devops-stack.io" rel="noopener noreferrer"&gt;DevOps Stack&lt;/a&gt; project,  a framework to deploy a standardized Kubernetes platform and its ecosystem, using a GitOps approach.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://www.slideshare.net/slideshow/embed_code/key/j0zNBoH48aFEO2" alt="j0zNBoH48aFEO2 on slideshare.net" width="100%" height="487"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Future Meetups
&lt;/h1&gt;

&lt;p&gt;Don’t miss any more and join the &lt;a href="https://www.meetup.com/Cloud-Native-Romandie" rel="noopener noreferrer"&gt;Cloud Native Romandie meetup group&lt;/a&gt;. This way, you can be part of a local community and stay up-to-date on different Cloud Native technologies.&lt;/p&gt;

&lt;p&gt;We look forward to meeting and exchanging with you during our next virtual meetup scheduled for Thursday, June 17th.&lt;/p&gt;

&lt;p&gt;Also, if you are keen to present a technology or you would like to see more of a technology, please let us know, we would be happy to support your interest. If it interests you, it also interests the community.&lt;/p&gt;

</description>
      <category>meetup</category>
      <category>cloudnative</category>
      <category>kubernetes</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Immutability &amp; loose coupling: a match made in heaven</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 18 Mar 2021 07:31:29 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/immutability-loose-coupling-a-match-made-in-heaven-37kl</link>
      <guid>https://dev.to/camptocamp-ops/immutability-loose-coupling-a-match-made-in-heaven-37kl</guid>
      <description>&lt;p&gt;When it comes to infrastructure and deployment automation, two opposite approaches share the podium: &lt;a href="https://www.digitalocean.com/community/tutorials/what-is-immutable-infrastructure" rel="noopener noreferrer"&gt;mutable vs immutable management&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutable Systems
&lt;/h2&gt;

&lt;p&gt;Mutable systems usually have a long life cycle, typically in the order&lt;br&gt;
of weeks to years. As their requirements change (new files,&lt;br&gt;
configurations, users, packages, etc.), the systems are modified to&lt;br&gt;
match a new target state. When left unmanaged, mutable systems tend to&lt;br&gt;
drift away from their target state, in a &lt;em&gt;divergent&lt;/em&gt; dynamic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11trbxkb1ev9goye0q2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11trbxkb1ev9goye0q2k.png" alt="Convergence Models in Mutable Systems" width="636" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Automating mutable systems is often referred to as Configuration Management, and leverages tools such as &lt;a href="https://cfengine.com/" rel="noopener noreferrer"&gt;Cfengine&lt;/a&gt;, &lt;a href="https://puppet.com/" rel="noopener noreferrer"&gt;Puppet&lt;/a&gt;, &lt;a href="https://www.chef.io/" rel="noopener noreferrer"&gt;Chef&lt;/a&gt;, or &lt;a href="https://www.ansible.com/" rel="noopener noreferrer"&gt;Ansible&lt;/a&gt;. This tooling uses principles based on the concepts of target state, idempotence, and somewhat related to &lt;a href="https://en.wikipedia.org/wiki/Promise_theory" rel="noopener noreferrer"&gt;Mark Burgess’ Promise Theory&lt;/a&gt;.  Configuration Management aims to make the system &lt;em&gt;convergent&lt;/em&gt;, by running a tool on a regular basis, in order to resynchronize the system with its target state. Some of these tools (e.g.  &lt;a href="https://github.com/purpleidea/mgmt" rel="noopener noreferrer"&gt;mgmt&lt;/a&gt;) also attempt to reach &lt;em&gt;congruence&lt;/em&gt; by adopting a reactive approach, triggering corrective actions on events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Immutable Systems
&lt;/h2&gt;

&lt;p&gt;In an immutable system, any change requires a new deployment. Whether it be a change in configuration, new files, or new users, immutability demands that the system be destroyed and rebuilt from scratch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhl5ewtyqpo5offh4p9jh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhl5ewtyqpo5offh4p9jh.png" alt="Trash full? Let's move to a new house!" width="400" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An immutable approach can greatly simplify deployments. Avoiding convergence, immutable systems rely on artifacts that are built once, and can be deployed multiple times. These artifacts can increase trust in the ability to rebuild the system from scratch if necessary. It can also ease scalability, since the artifact can be precisely duplicated.&lt;/p&gt;

&lt;p&gt;However, immutability comes with a high cost: in order to be done properly, it must be strict. Any change to the state involves a complete replacement of the artifact. A lack of abiding to that rule results in a convergent system (at best), which cannot be managed in an immutable manner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutable vs Immutable
&lt;/h2&gt;

&lt;p&gt;If immutable systems are easier to maintain, what are the reasons for not using them?  The system’s complexity is probably the main justification. A traditional mutable system features multiple layers (Operating System, Middleware, Applications) that are usually strongly coupled. For example, the flavor and version of the Operating System define which version of a Middleware (e.g. Tomcat, Apache) is available for installation. In turn, the Middleware version defines which libraries are available for the Application. On most Unix-based systems, shared libraries are at the root of strong links between software versions, based on the underlying ABIs required to run them.&lt;/p&gt;

&lt;p&gt;If such strongly coupled systems are to be managed in an immutable manner, then the &lt;em&gt;whole system&lt;/em&gt; is the immutable artifact. In the majority of organizations, this implies managing tens to thousands of artifacts, and rebuilding them from scratch on a regular basis. Such complexity is too much of a cost.&lt;/p&gt;

&lt;p&gt;Enters decoupling technologies: Over the years, new technologies have surfaced, which allow to decouple system components and ease their management in an immutable manner.&lt;/p&gt;

&lt;h1&gt;
  
  
  Virtualization and IaaS
&lt;/h1&gt;

&lt;p&gt;With the rise of virtualization in the early years of the 21st century, it became easier to decouple the hardware from the Operating System. You could now size virtual machines as precisely as desired, in terms of CPU, memory, or disk, without adding or replacing any physical device.&lt;/p&gt;

&lt;p&gt;This unlocked access to a first level of automated immutability, using Virtual Machine images as the immutable artifact. Image generators (such as Hashicorp Packer) appeared, easing the generation of VM images.&lt;/p&gt;

&lt;p&gt;Provided the whole target state —including the OS, middleware and application itself— is built into the VM image, an immutable workflow can be used to manage it. In this case, whenever a change is required, the whole image needs to be rebuilt and redeployed to new VMs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0gu5n8xhs57jvxnkn3ay.jpg" class="article-body-image-wrapper"&gt;&lt;img alt="Golden Images are a common approach to divergent templating" src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0gu5n8xhs57jvxnkn3ay.jpg" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;Golden Images are a common approach to divergent templating
  &lt;/p&gt;

&lt;p&gt;For a time, this deployment could not be easily automated, as physical nodes still needed to be manually picked before deploying the VM images.  Infrastructure as a Service (IaaS), often referred to as “Cloud”, changed that.&lt;/p&gt;

&lt;p&gt;IaaS provided APIs on top of virtualization, and the IaaS system (e.g.  AWS EC2, OpenStack) would often pick the physical hypervisor node itself, making it possible to fully automate the deployment of new artifacts (such as VM images).&lt;/p&gt;

&lt;p&gt;One important problem remained to achieve immutable infrastructure in most situations: the complexity of the artifact itself.&lt;/p&gt;

&lt;h1&gt;
  
  
  Containers and Orchestrators
&lt;/h1&gt;

&lt;p&gt;When setting up applications on existing systems, several issues often arise, among which are: packaging, configuration, and dependencies.&lt;/p&gt;

&lt;p&gt;For years, developers and systems engineers tried to solve the problem of application packaging and deployment using all kinds of package managers, from deb/rpm to homemade systems. This often failed, because these packages didn’t allow for multiple instantiation of the application, were not easy to configure, and were too tightly coupled to the rest of the system.&lt;/p&gt;

&lt;p&gt;Docker containers provided a unified way of packaging applications, in the form of OCI images, a rather unified way of configuring them (using environment variables or mounted files, in the &lt;a href="https://12factor.net/" rel="noopener noreferrer"&gt;12 factor app&lt;/a&gt; fashion).&lt;/p&gt;

&lt;p&gt;But mainly, containers provided an abstraction level, a decoupling from the system. With containers, it doesn’t matter anymore which OS is running the container engine. Developers can now choose to run any version of Tomcat or Apache, on any node with a container engine. As a corollary, they can also run any combination of Middleware and Applications, regardless of the libraries provided on the underlying system.&lt;/p&gt;

&lt;p&gt;Furthermore, containers were made to be managed in an immutable manner, using OCI images as immutable artifacts. Every time a container needs to be modified, it requires the creation of a new container from a new image.&lt;/p&gt;

&lt;p&gt;The benefits are huge. With this decoupling of the Operating System from the Middleware and Applications, the monolithic immutable artifact that was previously managed as a VM image can now be broken down into many pieces: the application is now an immutable artifact, and so are the middleware components as well.&lt;/p&gt;

&lt;p&gt;Even better: since all the components running on the machines are now immutable, the machines themselves have now become totally neutral; all they require is a container engine in order to run containers.&lt;/p&gt;

&lt;p&gt;One thing can still make a node a snowflake: manually deployed containers, using tools such as Docker or Docker-compose.&lt;/p&gt;

&lt;p&gt;What IaaS did for VMs, Container orchestrators now do for containers: they provide an API to orchestrate the dynamic deployment of containers on a cluster of nodes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4m11g0j9tcebfi9zlic.png" class="article-body-image-wrapper"&gt;&lt;img alt="Container Orchestration is to Containers what IaaS is to Virtual Machines" src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4m11g0j9tcebfi9zlic.png" width="800" height="556"&gt;&lt;/a&gt;&lt;br&gt;Container Orchestration is to Containers what IaaS is to Virtual Machines.
  &lt;/p&gt;

&lt;p&gt;The nodes are thus totally neutral now. As a result, their templates are greatly simplified, and they can now easily be managed in an immutable way. This new paradigm opened the way to new Operating Systems specialized for container orchestration, such as &lt;a href="https://getfedora.org/en/coreos?stream=stable" rel="noopener noreferrer"&gt;CoreOS&lt;/a&gt; or &lt;a href="https://rancher.com/docs/os/v1.x/en/" rel="noopener noreferrer"&gt;RancherOS&lt;/a&gt;, whose life cycles are meant to be managed with an immutable workflow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Immutability &amp;amp; Convergence
&lt;/h1&gt;

&lt;p&gt;Now that we have a full Immutable System, does it solve the problem of convergence?&lt;/p&gt;

&lt;p&gt;Immutable artifacts in themselves are not supposed to evolve, but their target state does evolve. In this regard, the situation with containers is similar to that of Golden Images for Virtual Machines: though the artifact is immutable, it can easily be used as a template leading to an unmaintained, divergent system.&lt;/p&gt;

&lt;p&gt;There is thus still a need for convergence tools in the container world.  However, this is not because the objects themselves drift from their target state. Rather, the target state evolves while the objects —supposedly— are stuck in their original state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyd2lcx01jxcgq1qiq6w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyd2lcx01jxcgq1qiq6w.png" alt="Convergence Models in Immutable Systems" width="636" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where Configuration Management tools ensure a convergence of states for mutable systems such as VMs, packaging tools such as &lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;Helm&lt;/a&gt; and &lt;a href="https://github.com/roboll/helmfile" rel="noopener noreferrer"&gt;Helmfile&lt;/a&gt; can be used to periodically re-synchronize containers and other immutable objects with their target state.&lt;/p&gt;

&lt;p&gt;Finally, congruence is also achievable, with tools such as &lt;a href="https://argoproj.github.io/argo-cd/" rel="noopener noreferrer"&gt;Argo CD&lt;/a&gt;. Argo CD not only deploys immutable objects to a Kubernetes cluster, but also keeps them synchronized with their last known target state, ensuring a continuous management.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Containers and Container Orchestrators enable fully immutable workflows for infrastructure, middleware and applications alike:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Nodes can be managed as virtual machines, with immutable VM images deployed dynamically using IaaS;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Middleware and applications can be managed as containers, with immutable OCI images deployed dynamically using Container Orchestrators.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Are immutable systems always the best answer? As we’ve seen, the cost in artifact management and orchestration is far from negligible. Due to the transient nature that comes with their immutability, containers are better suited for stateless applications that easily scale on clusters of neutral nodes. For this reason, highly stateful applications with long life cycles, such as databases, are still better maintained as mutable systems most of the time.&lt;/p&gt;

&lt;p&gt;Choosing an immutable vs mutable architecture depends a lot on an organization’s software architecture and culture, and is not a light choice to make. Is immutable infrastructure the solution to your automation problems? Contact us, we can help you find out!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>containers</category>
      <category>immutability</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Open Source, Standards, and Technical Debt</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Wed, 03 Feb 2021 11:10:41 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/open-source-standards-and-technical-debt-2g1</link>
      <guid>https://dev.to/camptocamp-ops/open-source-standards-and-technical-debt-2g1</guid>
      <description>&lt;p&gt;Twenty years ago, Camptocamp was a pioneer company in Open Source adoption. Nowadays, &lt;a href="https://ieeexplore.ieee.org/document/8880574" rel="noopener noreferrer"&gt;Open Source has become mainstream&lt;/a&gt; and the vast majority of the industry agrees on the many benefits of its practices. In fact, the Open Source model has become a &lt;em&gt;de facto&lt;/em&gt; standard in some fields such as Web Frontend development.&lt;/p&gt;

&lt;p&gt;Many companies make an increasing use of Open Source software in their infrastructure and development stacks, and there are countless proven reasons for doing so, such as standard formats or &lt;a href="https://www.forbes.com/sites/martenmickos/2018/09/26/why-openness-is-the-greatest-path-to-security/?sh=567640cf5f7f" rel="noopener noreferrer"&gt;security by openness&lt;/a&gt;, to name just a couple.&lt;/p&gt;

&lt;p&gt;In spite of these benefits, companies openly contributing —let alone Open Sourcing their own projects— are still somehow not very common, and most firms think of Open Source purely as a consumer’s benefit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdqdagbzcqm5kgkbcx4qz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdqdagbzcqm5kgkbcx4qz.png" alt="Open Source Community | © Shutterstock" width="585" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So why should you contribute to Open Source software?
&lt;/h2&gt;

&lt;p&gt;For years, I used to think the best argument in favor of contributing to existing projects was maintenance and compatibility. If I fork a project and add functionality to it, there is a risk that my changes will become increasingly hard to maintain as time goes by. If the core developers of the program are aware of my changes and actively intend to go along with them, this risk will be greatly reduced.&lt;/p&gt;

&lt;p&gt;So contributing my changes ensures they will stay compatible with the base code as time goes by. There might even be improvements to my code if more people encounter a similar need in the future, and decide to build on top of my changes.&lt;/p&gt;

&lt;p&gt;Today, however, I believe the example I have just given is a specific case of a more general rule, which encompasses more pragmatic reasons to contribute code as Open Source. This more general context is linked to the concept of &lt;a href="https://www.linuxfoundation.org/en/resources/publications/solving-technical-debt-with-open-source/" rel="noopener noreferrer"&gt;Technical Debt&lt;/a&gt;: the idea that technical decisions imply a hidden cost (a “debt”) that will have to be paid in the future in order to catch up with state-of-the-art technology.&lt;/p&gt;

&lt;h2&gt;
  
  
  So how do I minimize the debt?
&lt;/h2&gt;

&lt;p&gt;Minimizing technical debt is a vast —and at times conflicting— subject. However, I think it is safe to assume that one way to reduce its risk is to stick to standards. The closer a project sticks to industry standards, the less likely it will have to be ported to another technological stack in the foreseeable future.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if the standards don’t meet my needs?
&lt;/h2&gt;

&lt;p&gt;When faced with a missing feature, most people’s reflex might be to start building a specific component to meet their use case. In the words of &lt;a href="https://hiredthought.com/2018/09/01/intro-to-wardley-mapping/" rel="noopener noreferrer"&gt;Strategy Theorist Simon Wardley&lt;/a&gt;, they’ll be shifting this component to the Genesis stage, making it more unpredictable —or even erratic—, less standard, and thus more prone to building up technical debt in time.&lt;/p&gt;

&lt;p&gt;There is another way though. If my need is not met, and it is in fact a valid need (which is a very important question to ask in the first place), then other people might have this need in the future. When they do, someone, somewhere, will create a new standard for this need. When this new standard becomes enforced, then will my specific component’s debt become obvious.&lt;/p&gt;

&lt;p&gt;So what if, instead of building a specific component to make up for the lack of standard, I set the new standard myself? Open Source lets you do just that! It gives you the opportunity to be the first one providing an open implementation to a generic need, and the chance to make it into the new standard. If that new standard catches on, you have not only solved your problem, but you also haven’t accumulated technical debt. In fact, you’re ahead of the other users, because you set the new standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, we’re no FAANG!
&lt;/h2&gt;

&lt;p&gt;Obviously, the majority of organizations can't afford to have engineers focusing on IETF RFCs or moving ISO standards to fit our needs.&lt;/p&gt;

&lt;p&gt;However, a standard doesn't have to be that complicated. Let’s say I use this popular CLI tool, but I need to specify an option which doesn’t exist yet. I could hack something around the generation of its configuration file to produce the options I need. Or I could patch that tool and add a new flag for my needs, and contribute that change back to the project. Chances are, if I need this option, some else does too.&lt;/p&gt;

&lt;p&gt;Now, every time someone has the need for that option, they’ll be using my new flag. I’ve contributed a new standard, and I haven’t made any technical debt on my side.&lt;/p&gt;

&lt;p&gt;It’s not the size of the steps that matters, it’s really the direction in which you take them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8z3stx204q4gut3w4coq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8z3stx204q4gut3w4coq.png" alt="Start with Open Source | © Shutterstock" width="585" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do I start?
&lt;/h2&gt;

&lt;p&gt;Open Source is not just a philosophy. It encompasses licensing issues, technology standards, culture, and much more.&lt;/p&gt;

&lt;p&gt;At Camptocamp, we’ve been committed to the Open Source approach for years.&lt;/p&gt;

&lt;p&gt;This means we have a habit of solving problems in generic terms and building new standards.&lt;/p&gt;

&lt;p&gt;It also means we have contacts in many Open Source communities, which allow us to brainstorm ideas and quickly contribute to projects, ensuring a fast feedback loop on our work.&lt;/p&gt;

&lt;p&gt;When we implement Open Source software for our clients, we actively seek to limit technological debt. Because we believe in a world of standards, we don’t want our clients to feel entirely stuck with a technological stack in the future. Or even with us!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>agile</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>A Simple Auth Proxy for EKS</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Wed, 11 Nov 2020 16:10:48 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/a-simple-auth-proxy-for-eks-24dh</link>
      <guid>https://dev.to/camptocamp-ops/a-simple-auth-proxy-for-eks-24dh</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/eks/" rel="noopener noreferrer"&gt;AWS EKS&lt;/a&gt; is a great option for a hosted Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;It is in particular easy to use for demos and training sessions.&lt;/p&gt;

&lt;p&gt;However, EKS authentication is based off AWS IAM, which means users need an AWS account. Authenticating to EKS typically involves calling the &lt;code&gt;aws eks get-token&lt;/code&gt; command in your &lt;code&gt;.kube/config&lt;/code&gt; so as to retrieve an authentication token.&lt;/p&gt;

&lt;p&gt;As we were setting up EKS for Kubernetes training, we needed a simple way for users without an AWS account to access the cluster, so we created a basic proxy service for the EKS &lt;code&gt;get-token&lt;/code&gt; action.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-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/aws-iam-authenticator-proxy" rel="noopener noreferrer"&gt;
        aws-iam-authenticator-proxy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A simple HTTP proxy to share AWS IAM authentication
    &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;aws-iam-authenticator HTTP Proxy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://hub.docker.com/r/camptocamp/aws-iam-authenticator-proxy/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/1c0720634be95174607359ad2428e0cd7273de32ba7fc25646be782c127ce269/68747470733a2f2f696d672e736869656c64732e696f2f646f636b65722f70756c6c732f63616d70746f63616d702f6177732d69616d2d61757468656e74696361746f722d70726f78792e737667" alt="Docker Pulls"&gt;&lt;/a&gt;
&lt;a href="https://goreportcard.com/report/github.com/camptocamp/aws-iam-authenticator-proxy" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e678da83820c9e1c8fe190b9aa56ef2fd2afac1e894c72d3a112415df57301e2/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f63616d70746f63616d702f6177732d69616d2d61757468656e74696361746f722d70726f7879" alt="Go Report Card"&gt;&lt;/a&gt;
&lt;a href="http://www.camptocamp.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7f4619d2ce1e303d90b4e65d7de85dacced7805603dcb7d53c395d41fd27e32d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f62792d63616d70746f63616d702d6662373034372e737667" alt="By Camptocamp"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Amazon Services require valid accounts to be used. This proxy allows external
users to access an AWS EKS cluster without requiring access to AWS credentials.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: the proxy does not implement any form of authentication. You are
responsible for implementing whatever security measure you wish to enforce in
front of it.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Example usage&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;In order to give access to an AWS EKS cluster without distribution credentials
you can start the proxy with the necessary credentials as well as the cluster ID. For example, using Docker:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;$ docker run --rm -p 8080:8080 \
             -e AWS_ACCESS_KEY_ID=&lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;AWS_ACCESS_KEY_ID&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; \
             -e AWS_SECRET_ACCESS_KEY=&lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;AWS_SECRET_ACCESS_KEY&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; \
             -e EKS_CLUSTER_ID=&lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;EKS_CLUSTER_ID&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; \
             -e PSK=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;mysecretstring&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; \
    camptocamp/aws-iam-authenticator-proxy:latest&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;You should then be able to retrieve authentication tokens for your user at
&lt;a href="http://localhost:8080" rel="nofollow noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If a PSK is passed, you will need to pass its value in the…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/camptocamp/aws-iam-authenticator-proxy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Deploying with Docker
&lt;/h2&gt;

&lt;p&gt;The proxy can be deployed using Docker, with AWS credentials, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;AWS_ACCESS_KEY_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;AWS_SECRET_ACCESS_KEY&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;EKS_CLUSTER_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;EKS_CLUSTER_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PSK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mysecretstring"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    camptocamp/aws-iam-authenticator-proxy:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rights on the cluster will depend on the user you chose to create the access key.&lt;/p&gt;

&lt;p&gt;The PSK is optional, and allows to secure the proxy a little bit.&lt;/p&gt;

&lt;p&gt;Once the proxy is started, you can access it at &lt;a href="http://localhost:8080?psk=mysecretstring" rel="noopener noreferrer"&gt;http://localhost:8080?psk=mysecretstring&lt;/a&gt;, so you can simply set your &lt;code&gt;~/.kube/config&lt;/code&gt; to use &lt;code&gt;curl&lt;/code&gt; instead of &lt;code&gt;aws&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;users&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;&amp;lt;cluster_name&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;client.authentication.k8s.io/v1alpha1&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-s&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://&amp;lt;your_ip&amp;gt;:8080/?psk=mysecretstring"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying in EKS
&lt;/h2&gt;

&lt;p&gt;Since you've got an EKS cluster in the first place, you might as well deploy the proxy in it.&lt;/p&gt;

&lt;p&gt;The repository provides a Helm chart for that, in the &lt;code&gt;k8s&lt;/code&gt; directory of the GitHub project.&lt;/p&gt;

&lt;p&gt;You can simply instantiate the chart with the following values:&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;eks_cluster_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;EKS_CLUSTER_ID&amp;gt;"&lt;/span&gt;
&lt;span class="na"&gt;psk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mysecretstring"&lt;/span&gt;
&lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;access_key_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;AWS_ACCESS_KEY_ID&amp;gt;"&lt;/span&gt;
  &lt;span class="na"&gt;secret_access_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;AWS_SECRET_ACCESS_KEY&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AWS credentials will be stored in a Kubernetes secret and passed to the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using
&lt;/h3&gt;

&lt;p&gt;However, since we're in AWS, we can also use &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html" rel="noopener noreferrer"&gt;IAM roles for service accounts&lt;/a&gt; and bypass the access keys altogether. This is a much cleaner approach.&lt;/p&gt;

&lt;p&gt;Here's how to do it, using Terraform to create the role and deploy the proxy.&lt;/p&gt;

&lt;p&gt;First, create a role linked to OIDC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"iam_assumable_role_aws_iam_authenticator_proxy"&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;"terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"&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;"3.3.0"&lt;/span&gt;
  &lt;span class="nx"&gt;create_role&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;number_of_role_policy_arns&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="nx"&gt;role_name&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws-iam-authenticator-proxy"&lt;/span&gt;
  &lt;span class="nx"&gt;provider_url&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_oidc_issuer_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"https://"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;oidc_fully_qualified_subjects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"system:serviceaccount:yournamespace:aws-iam-authenticator-proxy"&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;replacing &lt;code&gt;yournamespace&lt;/code&gt; with the Kubernetes namespace where you will be deploying the proxy.&lt;/p&gt;

&lt;p&gt;Now we can configure the cluster to use map that role to the Kubernetes role we want (e.g. &lt;code&gt;system::masters&lt;/code&gt; to make it cluster admin).&lt;/p&gt;

&lt;p&gt;We'll create a random PSK and generate the Kubeconfig file to use &lt;code&gt;curl&lt;/code&gt; with the proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet_ids"&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&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;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/role/internal-elb"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"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;"terraform-aws-modules/eks/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;"13.1.0"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.18"&lt;/span&gt;

  &lt;span class="nx"&gt;subnets&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&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;enable_irsa&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;map_roles&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="nx"&gt;rolearn&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam_assumable_role_aws_iam_authenticator_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this_iam_role_arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam_assumable_role_aws_iam_authenticator_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this_iam_role_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;groups&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"system:masters"&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="nx"&gt;worker_groups&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="nx"&gt;instance_type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"m5a.large"&lt;/span&gt;
      &lt;span class="nx"&gt;asg_desired_capacity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="nx"&gt;asg_max_size&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;kubeconfig_aws_authenticator_command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"curl"&lt;/span&gt;
  &lt;span class="nx"&gt;kubeconfig_aws_authenticator_command_args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"-s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"https://${var.auth_url}/?psk=${random_password.auth_proxy_psk.result}"&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"auth_proxy_psk"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can deploy the proxy in Kubernetes using Helm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"helm_release"&lt;/span&gt; &lt;span class="s2"&gt;"aws-iam-authenticator-proxy"&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;"aws-iam-authenticator-proxy"&lt;/span&gt;
  &lt;span class="nx"&gt;chart&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/camptocamp/aws-iam-authenticator-proxy/tree/master/k8s"&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws-iam-authenticator-proxy"&lt;/span&gt;
  &lt;span class="nx"&gt;dependency_update&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;create_namespace&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tr&lt;/span&gt;

  &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&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;EOT&lt;/span&gt;&lt;span class="sh"&gt;
eks_cluster_id: "${var.cluster_name}"
psk: "${random_password.auth_proxy_psk.result}"
serviceAccount:
  name: "aws-iam-authenticator-proxy"
  annotations:
    eks.amazonaws.com/role-arn: ${module.iam_assumable_role_aws_iam_authenticator_proxy.this_iam_role_arn}
&lt;/span&gt;&lt;span class="no"&gt;EOT
&lt;/span&gt;  &lt;span class="p"&gt;]&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can add an &lt;code&gt;Ingress&lt;/code&gt; or configure the &lt;code&gt;Service&lt;/code&gt; to use an L4 &lt;code&gt;LoadBalancer&lt;/code&gt; by tuning the Helm values.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Colored wrappers for kubectl</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Tue, 06 Oct 2020 19:50:58 +0000</pubDate>
      <link>https://dev.to/raphink/colored-wrappers-for-kubectl-2pj1</link>
      <guid>https://dev.to/raphink/colored-wrappers-for-kubectl-2pj1</guid>
      <description>&lt;p&gt;When using Kubernetes, &lt;code&gt;kubectl&lt;/code&gt; is the command we use the most to visualize and debug objects.&lt;/p&gt;

&lt;p&gt;However, it currently does not support colored output, though there is &lt;a href="https://github.com/kubernetes/kubectl/issues/524" rel="noopener noreferrer"&gt;a feature request opened for this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's see how we can add color support. I'll be using zsh with &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;oh my zsh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Edit:&lt;/em&gt; this feature was &lt;a href="https://github.com/ohmyzsh/ohmyzsh/pull/9316" rel="noopener noreferrer"&gt;merged in oh my zsh&lt;/a&gt;, so it is now standard.&lt;/p&gt;

&lt;h1&gt;
  
  
  Zsh plugin
&lt;/h1&gt;

&lt;p&gt;Let's make this extension into a zsh plugin called &lt;code&gt;kubectl_color&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.oh-my-zsh/custom/plugins/kubectl_color
❯ &lt;span class="nb"&gt;touch&lt;/span&gt; ~/.oh-my-zsh/custom/plugins/kubectl_color/kubectl_color.plugin.zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we need to fill in this plugin.&lt;/p&gt;
&lt;h2&gt;
  
  
  JSON colorizing
&lt;/h2&gt;

&lt;p&gt;Let's start with JSON, by adding an alias that colorizes JSON output using the infamous &lt;a href="https://stedolan.github.io/jq/" rel="noopener noreferrer"&gt;&lt;code&gt;jq&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kj&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  kubectl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq
&lt;span class="o"&gt;}&lt;/span&gt;

compdef &lt;span class="nv"&gt;kj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;compdef&lt;/code&gt; line ensures the &lt;code&gt;kj&lt;/code&gt; function gets autocompleted just like &lt;code&gt;kubectl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Edit:&lt;/em&gt; I've added another wrapper for &lt;a href="https://github.com/antonmedv/fx" rel="noopener noreferrer"&gt;&lt;code&gt;fx&lt;/code&gt;&lt;/a&gt;, which provides a dynamic way to parse JSON:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kjx&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  kubectl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | fx
&lt;span class="o"&gt;}&lt;/span&gt;

compdef &lt;span class="nv"&gt;kjx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  YAML colorizing
&lt;/h2&gt;

&lt;p&gt;Just like for JSON, we can use &lt;a href="https://stedolan.github.io/jq/" rel="noopener noreferrer"&gt;&lt;code&gt;yh&lt;/code&gt;&lt;/a&gt; to colorize YAML output:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ky&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  kubectl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | yh
&lt;span class="o"&gt;}&lt;/span&gt;

compdef &lt;span class="nv"&gt;ky&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Energize!
&lt;/h1&gt;

&lt;p&gt;Our plugin is now ready, we only need to activate it in &lt;code&gt;~/.zshrc&lt;/code&gt; by adding it to the list of plugins, e.g.:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;git ruby kubectl kubectl_color&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="ltag_asciinema"&gt;
  
&lt;/div&gt;



&lt;p&gt;and with &lt;code&gt;fx&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_asciinema"&gt;
  
&lt;/div&gt;


</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>cli</category>
      <category>zsh</category>
    </item>
    <item>
      <title>Simple secret sharing with gopass and summon</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Tue, 28 Jul 2020 16:34:57 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/simple-secret-sharing-with-gopass-and-summon-40jk</link>
      <guid>https://dev.to/camptocamp-ops/simple-secret-sharing-with-gopass-and-summon-40jk</guid>
      <description>&lt;p&gt;Secrets are a fundamental, yet complex issue in software deployment.&lt;/p&gt;

&lt;p&gt;Solutions such as &lt;a href="https://www.keepassx.org/" rel="noopener noreferrer"&gt;KeepassX&lt;/a&gt; are simple to use, but quite impractical when it comes to automation.&lt;/p&gt;

&lt;p&gt;More complex options like &lt;a href="https://www.vaultproject.io/" rel="noopener noreferrer"&gt;Hashicorp Vault&lt;/a&gt; are extremely powerful, but harder to set up and maintain.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pass: a simple solution
&lt;/h1&gt;

&lt;p&gt;When it comes to storing securely and sharing passwords in a team, it is hard to come up with a more simple and efficient solution than Git and GnuPG combined.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.passwordstore.org/" rel="noopener noreferrer"&gt;Pass&lt;/a&gt; is a shell script that does just that. Inside a Git repository, Pass stores passwords in individual files encrypted for all private GnuPG keys in the team. It features a CLI to manipulate passwords, add new entries, or search through existing passwords.&lt;/p&gt;

&lt;h1&gt;
  
  
  More features
&lt;/h1&gt;

&lt;p&gt;However, Pass is quite limited in its features, so another project was born a few years later, to provide a new Go implementation of the Pass standard. Its name: simply &lt;a href="https://github.com/gopasspw/gopass" rel="noopener noreferrer"&gt;Gopass&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1cs4zelpc542tzc2ligm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1cs4zelpc542tzc2ligm.png" alt="Gopass Logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing
&lt;/h2&gt;

&lt;p&gt;Gopass is provided as binaries you can download from the releases page &lt;a href="https://github.com/gopasspw/gopass/releases" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Here are some of the features that make Gopass a great tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple mounts
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;pass&lt;/code&gt; allows you to have a single Git repository with your passwords, Gopass lets you create multiple repositories called "mounts", which is very useful when you want to share different secrets with different people:&lt;/p&gt;

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

&lt;span class="nv"&gt;$ &lt;/span&gt;gopass mounts
gopass &lt;span class="o"&gt;(&lt;/span&gt;/home/raphink/.password-store&lt;span class="o"&gt;)&lt;/span&gt;
├── c2c &lt;span class="o"&gt;(&lt;/span&gt;/home/raphink/.password-store-c2c&lt;span class="o"&gt;)&lt;/span&gt;
├── perso &lt;span class="o"&gt;(&lt;/span&gt;/home/raphink/.password-store-perso&lt;span class="o"&gt;)&lt;/span&gt;
└── terraform &lt;span class="o"&gt;(&lt;/span&gt;/home/raphink/.password-store-terraform&lt;span class="o"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Gopass uses a prefix to access secrets in mounts, so &lt;code&gt;terraform/puppet/c2c&lt;/code&gt; actually refers to the secret stored in &lt;code&gt;/home/raphink/.password-store-terraform/puppet/c2c.gpg&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple users
&lt;/h3&gt;

&lt;p&gt;Each Git repository can be set to encrypt passwords for multiple GnuPG keys.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.gpg-id&lt;/code&gt; file at the root of each repository contains the list of public keys to use for encryption, and the &lt;code&gt;.public-keys/&lt;/code&gt; directory keeps a copy of each key, making it easy for collaborators to import them into their keyring before they can start encrypting passwords for the team.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fuzzy search
&lt;/h3&gt;

&lt;p&gt;Gopass helps you find entries when the key you gave it doesn't match an exact known path:&lt;/p&gt;

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

&lt;span class="nv"&gt;$ &lt;/span&gt;gopass jenkins
&lt;span class="o"&gt;[&lt;/span&gt; 0] c2c/c2c_aws/jenkins-c2c
&lt;span class="o"&gt;[&lt;/span&gt; 1] c2c/c2c_mgmtsrv/freeipa/c2c-jenkins-swarm
&lt;span class="o"&gt;[&lt;/span&gt; 2] c2c/c2c_mgmtsrv/freeipa/jenkins-test-users
&lt;span class="o"&gt;[&lt;/span&gt; 3] perso/Devel/jenkins-ci.org
&lt;span class="o"&gt;[&lt;/span&gt; 4] terraform/aws/jenkins-c2c

Found secrets - Please &lt;span class="k"&gt;select &lt;/span&gt;an entry &lt;span class="o"&gt;[&lt;/span&gt;0]: 


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Structured secrets
&lt;/h3&gt;

&lt;p&gt;When decrypting a password, Gopass parses the content into two different parts: a password and a YAML document. For example, the content of a secret could look like this:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="s"&gt;foo&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;key1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;value1&lt;/span&gt;
&lt;span class="na"&gt;another_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;baz&lt;/span&gt;


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Password
&lt;/h4&gt;

&lt;p&gt;The first line of the content is the &lt;code&gt;password&lt;/code&gt;. If this is all you're interested in, you can use &lt;code&gt;gopass show --password&lt;/code&gt; to retrieve it:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;gopass show &lt;span class="nt"&gt;--password&lt;/span&gt; perso/test
foo


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Querying keys
&lt;/h4&gt;

&lt;p&gt;When the second part of the content (lines 2 and following) is a valid YAML document, you can query these values by providing a key, for example:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;gopass show perso/test key1
value1


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

&lt;/div&gt;

&lt;p&gt;Starting with Gopass 1.9.3, you can also query subkeys using either a dot or slash notation:&lt;/p&gt;

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

&lt;span class="nv"&gt;$ &lt;/span&gt;gopass show perso/test another_key.bar
baz
&lt;span class="nv"&gt;$ &lt;/span&gt;gopass show perso/test /another_key/bar
baz


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

&lt;/div&gt;

&lt;p&gt;This makes it extremely powerful to store several fields in the same secret.&lt;/p&gt;

&lt;h3&gt;
  
  
  TOTP
&lt;/h3&gt;

&lt;p&gt;Gopass allows to store TOTP keys alongside passwords. For example, you can have the following secret, stored at &lt;code&gt;terraform/service.io/api&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="s"&gt;WPTmU`&amp;gt;b&amp;lt;Y31&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;WPTmU`&amp;gt;b&amp;lt;Y31'&lt;/span&gt;
&lt;span class="na"&gt;totp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PIJ6AIHETAHSHOO7SHEI1AEK6IH1SOOCHATUOSH8XUAN0OOTH9XAHRUXO4AHJAEVI'&lt;/span&gt;
&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://myservice.io&lt;/span&gt;
&lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jdoe&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;In addition to retrieving each field with the corresponding key, you can also generate TOTP tokens with &lt;code&gt;gopass totp&lt;/code&gt;:&lt;/p&gt;

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

&lt;span class="nv"&gt;$ &lt;/span&gt;gopass totp terraform/service.io/api
568000 lasts 18s    |------------&lt;span class="o"&gt;==================&lt;/span&gt;|


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Integrations
&lt;/h2&gt;

&lt;p&gt;Gopass can be easily integrated into projects for deployments or CI/CD tasks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Summon
&lt;/h3&gt;

&lt;p&gt;The easiest way to integrate Gopass is probably to use &lt;a href="https://github.com/cyberark/summon" rel="noopener noreferrer"&gt;Summon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgy0cw2iqwdb85lobl5jl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgy0cw2iqwdb85lobl5jl.png" alt="Summon logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Summon is a tool which dynamically exposes environment variables with values retrieved from various secret stores. &lt;code&gt;gopass&lt;/code&gt; is one of its possible providers.&lt;/p&gt;
&lt;h4&gt;
  
  
  Setup
&lt;/h4&gt;

&lt;p&gt;Setting it up to use &lt;code&gt;gopass&lt;/code&gt; is rather straightforward. We use a simple wrapper called &lt;code&gt;summon-gopass&lt;/code&gt;, which needs to be in your PATH:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
gopass show &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;|tr : &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;You can also simply make &lt;code&gt;summon-gopass&lt;/code&gt; a symbolic link to your &lt;code&gt;gopass&lt;/code&gt; binary, but subkeys won't work in this case.&lt;/p&gt;

&lt;h4&gt;
  
  
  Usage
&lt;/h4&gt;

&lt;p&gt;Summon lets you provide a local &lt;code&gt;secrets.yml&lt;/code&gt; file which defines which environment variables you wish to define, and how to find the values.&lt;/p&gt;

&lt;p&gt;Here's a simple example of a &lt;code&gt;secrets.yml&lt;/code&gt; file using the secret we defined earlier:&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;SERVICE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!var&lt;/span&gt; &lt;span class="s"&gt;terraform/service.io/api url&lt;/span&gt;
&lt;span class="na"&gt;USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!var&lt;/span&gt; &lt;span class="s"&gt;terraform/service.io/api username&lt;/span&gt;
&lt;span class="na"&gt;SERVICE_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!var&lt;/span&gt; &lt;span class="s"&gt;terraform/service.io/api password&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;You can test this setup by running the following command in the directory containing &lt;code&gt;secrets.yml&lt;/code&gt;:&lt;/p&gt;

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

&lt;span class="nv"&gt;$ &lt;/span&gt;summon &lt;span class="nb"&gt;env&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The output should contain the 3 variables with the values stored in Gopass.&lt;/p&gt;

&lt;h4&gt;
  
  
  Exposing files
&lt;/h4&gt;

&lt;p&gt;While the format above allows you to expose simple secrets as variables, it is not very practical when you need secrets exposed as files.&lt;/p&gt;

&lt;p&gt;Summon covers this need however, using the &lt;code&gt;file&lt;/code&gt; flag. For example:&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;SSH_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!var:file&lt;/span&gt; &lt;span class="s"&gt;terraform/service.io/ssh private_key&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;If &lt;code&gt;terraform/service.io/ssh&lt;/code&gt; is a secret in Gopass whose &lt;code&gt;private_key&lt;/code&gt; YAML field contains an SSH private key, then Summon will extract this secret, place it into a temporary file (in &lt;code&gt;/dev/shm&lt;/code&gt; by default) and set the &lt;code&gt;SSH_KEY&lt;/code&gt; variable with the path to the file. After the command returns, the temporary file will be delete.&lt;/p&gt;

&lt;p&gt;You could then use such a &lt;code&gt;secrets.yml&lt;/code&gt; file with:&lt;/p&gt;

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

summon sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'ssh -i $SSH_KEY user@service'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Another useful example is to store a Kubernetes cluster configuration in Gopass, e.g.:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;clusters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://k8s.example.com&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;k8s&lt;/span&gt;
&lt;span class="na"&gt;contexts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default-cluster-admin&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;default-admin&lt;/span&gt;
&lt;span class="na"&gt;current-context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default-admin&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Config&lt;/span&gt;
&lt;span class="na"&gt;preferences&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;users&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;default-cluster-admin&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;averylongtoken&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;With the following &lt;code&gt;secrets.yml&lt;/code&gt; file:&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;KUBECONFIG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!var:file&lt;/span&gt; &lt;span class="s"&gt;path/to/secret&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;You can then work on the Kubernetes cluster with &lt;code&gt;kubectl&lt;/code&gt; using:&lt;/p&gt;

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

&lt;span class="nv"&gt;$ &lt;/span&gt;summon kubectl &amp;lt;some &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Terraform integration
&lt;/h3&gt;

&lt;p&gt;A simple way to pass variables to Terraform is to declare them and use &lt;code&gt;summon&lt;/code&gt; to pass the values:&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;TF_VAR_var1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!var&lt;/span&gt; &lt;span class="s"&gt;terraform/project1/secret1 field1&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;You can then run &lt;code&gt;summon terraform&lt;/code&gt; to dynamically pass these secrets to Terraform.&lt;/p&gt;

&lt;p&gt;Another possibility is to use &lt;a href="https://github.com/camptocamp/terraform-provider-pass" rel="noopener noreferrer"&gt;Camptocamp's Terraform Pass Provider&lt;/a&gt; which lets you retrieve and set passwords in Gopass natively in Terraform:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"pass"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;store_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/srv/password-store"&lt;/span&gt;    &lt;span class="c1"&gt;# defaults to $PASSWORD_STORE_DIR&lt;/span&gt;
  &lt;span class="nx"&gt;refresh_store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;                &lt;span class="c1"&gt;# do not call `git pull`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Store a value into the Gopass store&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"pass_password"&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"secret/foo"&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0123456789"&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;zip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"zap"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Retrieve password at another_secret/bar to be used in Terraform code&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"pass_password"&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"another_secret/bar"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The provider exposes the secret with the following properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;path&lt;/code&gt;: path to the secret&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;password&lt;/code&gt;: secret password (first line of the content)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt;: a structure (map) of the YAML data in the content&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;body&lt;/code&gt;: the content found on lines 2 and following, if it could not be parsed as YAML &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;full&lt;/code&gt;: the full content (all lines) of the secret&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hiera Integration
&lt;/h3&gt;

&lt;p&gt;The most standard way to store secrets in Hiera is to use &lt;a href="https://github.com/voxpupuli/hiera-eyaml" rel="noopener noreferrer"&gt;&lt;code&gt;hiera-eyaml&lt;/code&gt;&lt;/a&gt;, which stores secret values encrypted inside YAML files, using either a PKCS7 key (default) or multiple GnuPG keys (when using &lt;a href="https://github.com/voxpupuli/hiera-eyaml-gpg" rel="noopener noreferrer"&gt;&lt;code&gt;hiera-eyaml-gpg&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If your passwords are already stored in Gopass, you might want to integrate that into Hiera instead.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/camptocamp/hiera-pass" rel="noopener noreferrer"&gt;&lt;code&gt;camptocamp/hiera-pass&lt;/code&gt; module&lt;/a&gt; provides two Hiera backends to retrieve keys either as full Gopass secrets, or as keys inside the secrets.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Decomissioning with Puppet: report &amp; purge unmanaged resources</title>
      <dc:creator>Raphaël Pinson</dc:creator>
      <pubDate>Thu, 23 Jul 2020 14:42:09 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/decomissioning-with-puppet-report-purge-unmanaged-resources-1jgk</link>
      <guid>https://dev.to/camptocamp-ops/decomissioning-with-puppet-report-purge-unmanaged-resources-1jgk</guid>
      <description>&lt;p&gt;Puppet lets you manage resources explicitely. But did you know you can also dynamically purge unmanaged resources using Puppet?&lt;/p&gt;

&lt;h1&gt;
  
  
  Why?
&lt;/h1&gt;

&lt;p&gt;A user in your organization just left, and you need to remove their account from all nodes. If you were managing their account with Puppet —whether with a &lt;code&gt;user&lt;/code&gt; resource type or using an &lt;a href="https://forge.puppet.com/modules?q=accounts" rel="noopener noreferrer"&gt;accounts module&lt;/a&gt;—, you need to make sure this user is absent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight puppet"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'jdoe'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="py"&gt;ensure&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;absent&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;Great. Job done. Now, how long should this resource be kept in your code? One hour? One week? One year? What if an old node that was turned off wakes up months from now with this account activated?&lt;/p&gt;

&lt;p&gt;To be honest, if a node turned off for months suddenly wakes up, you'll probably have more issues than just old users if your Puppet code base is quite active…&lt;br&gt;
However, purging all unknown users would be a much easier approach than managing them explicitely!&lt;/p&gt;
&lt;h1&gt;
  
  
  How?
&lt;/h1&gt;

&lt;p&gt;As explained &lt;a href="https://dev.to/camptocamp-ops/how-to-manage-files-with-puppet-55e4#whole-dynamic-purge"&gt;in a previous post about managing files in Puppet&lt;/a&gt;, Puppet has the ability of purging unmanaged resources. I'll let you see the post for more explanations on how this works:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/camptocamp-ops" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2283%2F3522f5a3-13f1-414d-9ece-79157cf56657.png" alt="Camptocamp Infrastructure Solutions" width="400" height="400"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F59811%2Fcdcdbc95-1306-4455-9f79-fa032c300206.jpeg" alt="" width="460" height="460"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/camptocamp-ops/how-to-manage-files-with-puppet-55e4" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;All the ways to manage files with Puppet&lt;/h2&gt;
      &lt;h3&gt;Raphaël Pinson for Camptocamp Infrastructure Solutions ・ Jun 8 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#puppet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#cfgmgmt&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;h1&gt;
  
  
  What if I don't want to purge?
&lt;/h1&gt;

&lt;p&gt;What if instead of purging, I'd just like Puppet to report the unmanaged resources but not do anything about them?&lt;/p&gt;

&lt;p&gt;Luckily for us, &lt;code&gt;noop&lt;/code&gt; works fine with the &lt;code&gt;purge&lt;/code&gt; type, so you can use something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight puppet"&gt;&lt;code&gt;&lt;span class="n"&gt;purge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="py"&gt;noop&lt;/span&gt;   &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="py"&gt;unless&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'uid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1000'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'=='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'nobody'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will mark all users with a UID above 999 (except the &lt;code&gt;nobody&lt;/code&gt; user) to be purged, but it won't do it. As a result, you'll get &lt;code&gt;noop&lt;/code&gt; resources in your reports, for example in Puppetboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3xu0q5i3e98bv27ih117.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3xu0q5i3e98bv27ih117.png" alt="Noop resources" width="473" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then in the report, you'll see the unmanaged users:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fckpsim9bx6igwp09it4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fckpsim9bx6igwp09it4l.png" alt="Report view" width="800" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Forcing purge
&lt;/h1&gt;

&lt;p&gt;If you see users that should be purged, you can add again a &lt;code&gt;user&lt;/code&gt; resource in your Puppet code to ensure their absence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight puppet"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'iperf'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="py"&gt;ensure&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;absent&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;Another option is to make it a bit more dynamic. I've added an option in my &lt;code&gt;accounts&lt;/code&gt; base class to use a dynamic fact to purge users on demand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight puppet"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;osbase::accounts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="nv"&gt;$purge_users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str2bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$facts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'purge_users'&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="n"&gt;purge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="py"&gt;noop&lt;/span&gt;   &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$purge_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="py"&gt;unless&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'uid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1000'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'=='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'nobody'&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;purge_users&lt;/code&gt; fact doesn't exist by default, so I can define it on the go when I need to purge users.&lt;br&gt;
Now I can run &lt;code&gt;puppet apply&lt;/code&gt; on a node and force purging the users with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ FACTER_purge_users=y puppet agent -t
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And all unmanaged users will be removed from the node!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Do you have specific Puppet needs? &lt;a href="https://www.camptocamp.com/contact" rel="noopener noreferrer"&gt;Contact us&lt;/a&gt;, we can help you!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>puppet</category>
      <category>devops</category>
      <category>cfgmgmt</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
