<?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: David McKay</title>
    <description>The latest articles on DEV Community by David McKay (@rawkode).</description>
    <link>https://dev.to/rawkode</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%2F88054%2F111602a7-1c56-4dc8-a5eb-1ea9aa84e24c.jpg</url>
      <title>DEV Community: David McKay</title>
      <link>https://dev.to/rawkode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rawkode"/>
    <language>en</language>
    <item>
      <title>Kubernetes SDKs from the Pulumiverse</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Mon, 31 Jan 2022 17:15:54 +0000</pubDate>
      <link>https://dev.to/pulumi/kubernetes-sdks-from-the-pulumiverse-2cpl</link>
      <guid>https://dev.to/pulumi/kubernetes-sdks-from-the-pulumiverse-2cpl</guid>
      <description>&lt;p&gt;Pulumi provides an amazingly rich interface for developers and operators to define their Kubernetes workloads, providing typed access to recourses from the Kubernetes API and allowing our IDEs to provide code completion and refactoring opportunities through the native language plugins.&lt;/p&gt;

&lt;p&gt;As great as that is, it's always gotten a little cumbersome when it comes to Custom Resource Definitions (CRDs), as the first option is to leverage the CustomResource escape-hatch that allows you to define any Kubernetes object you wish; however this does mean we lose the rich interface we've become accustomed to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiextensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CustomResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marvin-the-martian&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;looneytunes.com/v1990&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LooneyTune&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Marvin the Martian&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;planet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mars&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;armor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hoplite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;phrases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Wheres the kaboom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Oh, drat these computers. They’re so naughty and so complex. I could pinch them&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I do so enjoy observing the flora and fauna of that tiny planet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please, sir. Do not interrupt my chain of thought. I am a busy Martian&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Brace yourself for immediate disintegration&lt;/span&gt;&lt;span class="dl"&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;While this is a huge unblocker for working with Kubernetes, we can do better.&lt;/p&gt;

&lt;h2&gt;
  
  
  crd2pulumi
&lt;/h2&gt;

&lt;p&gt;Back in August, 2020, we published &lt;a href="https://dev.to%7B%7B&amp;lt;%20relref%20"&gt;}}"&amp;gt;crd2pulumi&lt;/a&gt;. &lt;code&gt;crd2pulumi&lt;/code&gt; allows you to generate a Pulumi SDK for any Kubernetes Custom Resource. Now, when you want to start using cert-manager, Ambassador, Linkerd, or any other project within the Kubernetes and Cloud Native space; you can download the CRD YAML and run &lt;code&gt;crd2pulumi&lt;/code&gt;, which will generate the SDK for whatever supported language you wish. Neat?&lt;/p&gt;

&lt;p&gt;I've been using this approach for the past year and it's the easiest way to provide that rich interface to developers, but the repetition can become a little frustrating. For every new project that I was working on, I'd need to build out the same automation and there's also the maintenance burden of ensuring the SDK is up to date with the latest version deployed to the cluster.&lt;/p&gt;

&lt;p&gt;Further more, these SDKs never change; if you generate a SDK for cert-manager and someone else does, supporting the same version - there's no difference in the code that is generated. Do we really need everyone to do this themselves?&lt;/p&gt;

&lt;p&gt;Perhaps some further tooling is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes SDKs at Pulumiverse
&lt;/h2&gt;

&lt;p&gt;I like solving problems like this. It's not technically challenging, the hard work is already done by the team supporting &lt;code&gt;crd2pulumi&lt;/code&gt;. The problem we need to solve here is a quality of life / developer experience issue. How can we make this easier for developers to consume?&lt;/p&gt;

&lt;p&gt;For us to make this easier, we have two issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How do we automate the generation of the SDKs, ensuring that the SDKs are always up to date?&lt;/li&gt;
&lt;li&gt;Where do we publish them?&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Automation
&lt;/h3&gt;

&lt;p&gt;GitHub Actions plays such a large role in our developer world these days, especially for open source projects. So it was an easy choice to make that any automation I build out for this will be built on top of GitHub Actions. Knowing that we want the SDKs to be up to date, we also know that this automation must run on a regular cadence. As such, the beginnnig of our GitHub Action can take some shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build SDKs&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3,15&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we have our GitHub Action scheduled to run every day at 3:10am and 3:10pm.&lt;/p&gt;

&lt;p&gt;Next, we need to know what to build. We don't want to build EVERY SDK that the project is aware of, so we need some code to look up GitHub Releases for some new tags. For this, we can drop to Python to do some quick lookups.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;atoma&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;parse_atom_bytes&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dumps&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;httpget&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;yaml&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;YAMLError&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;has_been_updated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;hours_since&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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="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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;12&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;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hours_since&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_new_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/tags.atom'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse_atom_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&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="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;has_been_updated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;


&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./sdks.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sdks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;YAMLError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Error parsing SDK Yaml: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&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;sdks_to_build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sdks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;get_new_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sdks_to_build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="si"&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdks_to_build&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Python will loop through our configured SDKs, loaded from a YAML document, and reach out to the atom feed that GitHub provides for every public repository. This allows us to quickly loop over the tags/releases and find any published within the last window. We don't need to do anything else besides print this to the terminal and allow GitHub Actions to store the output for use in our build matrix.&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;find_new_tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;... random boring steps&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;find_new_tags&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "::set-output name=sdk_versions::$(poetry run python ./bin/find-new-tags.py ${{ github.event.inputs.since_hours }})"&lt;/span&gt;

    &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;sdk_versions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.find_new_tags.outputs.sdk_versions }}&lt;/span&gt;

&lt;span class="na"&gt;build_nodejs_sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;find_new_tags&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ fromJson(needs.find_new_tags.outputs.sdk_versions) }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once our Python script has run and the output stored, we're using a helper function from GitHub called &lt;code&gt;fromJson&lt;/code&gt; to use the output as a dynamic input to their matrix build system. We'll get exactly one job for every new tag that we've found.&lt;/p&gt;

&lt;p&gt;The job to generate the SDK and publish it to npm looks like this:&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="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;crd2pulumi-generate-sdk&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;IFS='|' read -r -a build_parts &amp;lt;&amp;lt;&amp;lt; "${{ matrix.version }}"&lt;/span&gt;
    &lt;span class="s"&gt;mkdir -p ./_output&lt;/span&gt;
    &lt;span class="s"&gt;curl -fsSL -o crd.yaml https://doc.crds.dev/raw/${build_parts[1]}@${build_parts[2]}&lt;/span&gt;
    &lt;span class="s"&gt;crd2pulumi --nodejsName ${build_parts[0]} --nodejsPath ./_output ./crd.yaml --force&lt;/span&gt;
    &lt;span class="s"&gt;# Fix Package Name&lt;/span&gt;
    &lt;span class="s"&gt;sed -ie "s#@pulumi/${build_parts[0]}#@pulumiverse/${build_parts[0]}#g" ./_output/package.json&lt;/span&gt;
    &lt;span class="s"&gt;# Fix Package Version&lt;/span&gt;
    &lt;span class="s"&gt;sed -ie "s#\"version\": \"\"#\"version\": \"${build_parts[2]}\"#g" ./_output/package.json&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm publish --access=public&lt;/span&gt;
  &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./_output&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;NODE_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NPM_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like I said earlier, the hard work was already done; we just needed to glue some automation together to improve the DX.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing
&lt;/h3&gt;

&lt;p&gt;Now that we have this system in place to regularly check and build new SDKs, we needed a home for them to live. Recently, some amazing Pulumi community members, worked together on a new GitHub initiative, with Pulumi support, called Pulumiverse. Pulumiverse aims to be a community lead and governed initiative to provide new abstractions and libraries for consumers of Pulumi to use and make their lives easier. This is the perfect place for these SDKs to live.&lt;/p&gt;

&lt;p&gt;You can checkout the &lt;a href="https://github.com/pulumiverse/kubernetes-sdks"&gt;repository today&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  SDKs
&lt;/h3&gt;

&lt;p&gt;Currently, the automation publishes NodeJS SDKs (JavaScript and TypeScript) to npm, but plans to add Python will be executed very soon. We're also looking to publish packages for dotNet, but research is still underway.&lt;/p&gt;

&lt;p&gt;I don't expect to publish packages for Go ... sadly, because Go modules use GitHub for fetching source code - we'd need to publish generated code to the repository and I'm not entirely keen on that approach. I could possibly be persuaded, but I'd love some suggestions for alternate ways to resolve this.&lt;/p&gt;

&lt;p&gt;We currently provide SDKs for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;argocd&lt;/li&gt;
&lt;li&gt;cert-managaer&lt;/li&gt;
&lt;li&gt;crossplane&lt;/li&gt;
&lt;li&gt;knative&lt;/li&gt;
&lt;li&gt;redpanda&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Need more? No problem! Open a pull request adding a SINGLE LINE to &lt;code&gt;sdks.yaml&lt;/code&gt; and the SDK will be available shortly.&lt;/p&gt;

&lt;p&gt;Nice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prior Art
&lt;/h3&gt;

&lt;p&gt;While writing this article, I discovered an &lt;a href="https://github.com/pulumi/pulumi-kubernetes-crds"&gt;old repository&lt;/a&gt; within the Pulumi organization that actually did something similar to this last year 😮 It's not automated like this new initiative, but it was great to find that previous attempts to improve the developer experience for Kubernetes teams using Pulumi. Mad props to Paul Stack and Albert Zhong.&lt;/p&gt;

&lt;p&gt;So that's it! Thanks for reading. We hope you find this useful and we encourage you to join us and help by adding your favourite Kubernetes SDKs to the automation.&lt;/p&gt;

&lt;p&gt;We'll see you in 2022, have a great New Year. 🎉&lt;/p&gt;

</description>
      <category>pulumi</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Introduction to OpenEBS</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Sat, 24 Oct 2020 09:56:20 +0000</pubDate>
      <link>https://dev.to/rawkode/introduction-to-openebs-565b</link>
      <guid>https://dev.to/rawkode/introduction-to-openebs-565b</guid>
      <description>&lt;p&gt;OpenEBS builds on Kubernetes to enable Stateful applications to easily access Dynamic Local PVs or Replicated PVs. By using the Container Attached Storage pattern users report lower costs, easier management, and more control for their teams.&lt;/p&gt;

&lt;p&gt;OpenEBS is a 100% Open Source CNCF project made with heart by MayaData &amp;amp; the community. Prominent users include Arista, Optoro, Orange, Comcast and the CNCF itself.&lt;/p&gt;

&lt;p&gt;🕰 Timeline&lt;/p&gt;

&lt;p&gt;00:00 - Holding Screen&lt;br&gt;
02:10 - Introductions&lt;br&gt;
03:35 - Slides - Introduction to OpenEBS&lt;br&gt;
16:15 - What is DPDK (Data Plane Development Kit) and SPDK (Storage Performance Development Kit)&lt;br&gt;
22:30 - My summary of what we've covered&lt;br&gt;
31:00 - Why Huge Pages / Enabling Huge Pages on Linux&lt;br&gt;
38:40 - Deploying OpenEBS with Mayastor&lt;br&gt;
44:00 - Fixing my unhealthy cluster&lt;br&gt;
47:30 - Adding the nvme kernel modules&lt;br&gt;
52:30 - Configuring Mayastor&lt;br&gt;
59:30 - Requesting a PersistentVolumeClaim&lt;br&gt;
1:11:30 - Deploying fio to run some benchmarks&lt;br&gt;
1:18:00 - Closing thoughts&lt;/p&gt;

&lt;p&gt;🌎 Resources&lt;/p&gt;

&lt;p&gt;Kiran Mova - &lt;a href="https://twitter.com/kiranmova"&gt;https://twitter.com/kiranmova&lt;/a&gt;&lt;br&gt;
Paul Burt - &lt;a href="https://twitter.com/idvoretskyi"&gt;https://twitter.com/idvoretskyi&lt;/a&gt;&lt;br&gt;
Jeffry Molanus - &lt;a href="https://twitter.com/JeffryMolanus"&gt;https://twitter.com/JeffryMolanus&lt;/a&gt;&lt;br&gt;
OpenEBS - &lt;a href="https://openebs.io/"&gt;https://openebs.io/&lt;/a&gt;&lt;/p&gt;

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

</description>
      <category>kubernetes</category>
      <category>storage</category>
      <category>csi</category>
      <category>devops</category>
    </item>
    <item>
      <title>Introduction to GitOps with GitOps Toolkit / Flux v2</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Sat, 24 Oct 2020 09:53:00 +0000</pubDate>
      <link>https://dev.to/rawkode/introduction-to-gitops-with-gitops-toolkit-flux-v2-4fdi</link>
      <guid>https://dev.to/rawkode/introduction-to-gitops-with-gitops-toolkit-flux-v2-4fdi</guid>
      <description>&lt;p&gt;GitOps is a way to do Kubernetes cluster management and application delivery.  It works by using Git as a single source of truth for declarative infrastructure and applications. With GitOps, the use of software agents can alert on any divergence between Git with what's running in a cluster, and if there's a difference, Kubernetes reconcilers automatically update or rollback the cluster depending on the case. With Git at the center of your delivery pipelines, developers use familiar tools to make pull requests to accelerate and simplify both application deployments and operations tasks to Kubernetes.&lt;/p&gt;

&lt;p&gt;The GitOps Toolkit is a set of composable APIs and specialised tools that can be used to build a Continuous Delivery platform on top of Kubernetes.&lt;/p&gt;

&lt;p&gt;These tools are build with Kubernetes controller-runtime libraries, and they can be dynamically configured with Kubernetes custom resources either by cluster admins or by other automated tools. The GitOps Toolkit components interact with each other via Kubernetes events and are responsible for the reconciliation of their designated API objects.&lt;/p&gt;

&lt;p&gt;🕰 Timeline&lt;/p&gt;

&lt;p&gt;00:00 - Holding Screen&lt;br&gt;
01:25 - Introductions&lt;br&gt;
02:00 - What is GitOps / GitOps Toolkit?&lt;br&gt;
05:00 - Should I use Flux v1 or GitOps Toolkit?&lt;br&gt;
07:45 - Bootstrapping GitOps Toolkit&lt;br&gt;
15:00 - What are the GitOps Toolkit components?&lt;br&gt;
17:40 - GitOps Toolkit CRDs&lt;br&gt;
21:00 - Suspending reconciliation&lt;br&gt;
23:30 - Deploying our first workload&lt;br&gt;
27:10 - Questions&lt;br&gt;
34:30 - Add another GitRepository&lt;br&gt;
43:30 - Dependencies and health-checks&lt;br&gt;
59:20 - Deploying Helm charts&lt;br&gt;
1:09:00 - Final questions&lt;/p&gt;

&lt;p&gt;🌎 Resources&lt;/p&gt;

&lt;p&gt;Stefan Prodan - &lt;a href="https://twitter.com/stefanprodan"&gt;https://twitter.com/stefanprodan&lt;/a&gt;&lt;br&gt;
GitOps Toolkit Soruce - &lt;a href="https://github.com/fluxcd/toolkit"&gt;https://github.com/fluxcd/toolkit&lt;/a&gt;&lt;br&gt;
GitOps Toolkit Docs - &lt;a href="https://toolkit.fluxcd.io"&gt;https://toolkit.fluxcd.io&lt;/a&gt;&lt;br&gt;
Walkthrough - &lt;a href="https://gist.github.com/stefanprodan/1f5e0b31303a95885221e5c7733fc639"&gt;https://gist.github.com/stefanprodan/1f5e0b31303a95885221e5c7733fc639&lt;/a&gt;&lt;/p&gt;

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

</description>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>devops</category>
    </item>
    <item>
      <title>QuestDB on Kubernetes</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Mon, 24 Aug 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/rawkode/questdb-on-kubernetes-4hhe</link>
      <guid>https://dev.to/rawkode/questdb-on-kubernetes-4hhe</guid>
      <description>&lt;p&gt;I was first introduced to QuestDB by my former colleague, &lt;a href="https://twitter.com/davidgsiot"&gt;David Simmons&lt;/a&gt;, who now runs their Developer Relations team. As I'm a rather curious individual that is interested in databases, especially time-series, I wanted to have a play with it.&lt;/p&gt;

&lt;p&gt;I don't, unless absolutely need to, have a JVM installed on my laptop. QuestDB is written in Java ... so I had two options: Docker or Kubernetes.&lt;/p&gt;

&lt;p&gt;I opted for Kubernetes, as there was no Helm chart currently available; this would give me a great opportunity to contribute to QuestDB.&lt;/p&gt;

&lt;p&gt;If you'd prefer to watch a video than read this blog, fear not! You can watch David and I run through this process in the following video, which also includes configuring Telegraf to write data to QuestDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing QuestDB on Kubernetes with Helm
&lt;/h2&gt;

&lt;p&gt;In order to install QuestDB to Kubernetes, we need to clone the Helm chart locally. This step is currently required because a Helm chart repository isn't currently being published by QuestDB yet, though this is something I expect to assist them with over the coming weeks with some further contributions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/questdb/questdb-kubernetes

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

&lt;/div&gt;



&lt;p&gt;The chart ships with a &lt;code&gt;values.yaml&lt;/code&gt; that should be familiar to anyone that's deployed a Kubernetes resource before. I cover each block in a little detail in the next section, should you be interested in that kinda thing.&lt;/p&gt;

&lt;p&gt;For those of you that don't care, lets get to the fun bit. The following command assumes we've run the &lt;code&gt;git clone&lt;/code&gt; above in the current working directory. If not, update the path at the end to wherever you cloned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm upgrade --install questdb ./questdb-kubernetes/charts/questdb

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

&lt;/div&gt;



&lt;p&gt;This command takes only a second, or two, and will print something like so to the terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Release "questdb" does not exist. Installing it now.
NAME: questdb
LAST DEPLOYED: Mon Aug 24 11:15:15 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=questdb,app.kubernetes.io/instance=questdb" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:9000 to use your application"
  kubectl --namespace default port-forward $POD_NAME 9000:9000

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Confirming the Install
&lt;/h3&gt;

&lt;p&gt;The first thing we'll want to do is ensure that the pods are scheduled and running. You may see the &lt;code&gt;STATUS&lt;/code&gt; as &lt;code&gt;ContainerCreating&lt;/code&gt; for a few moments, this means that the container image is still being pulled into your cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get pods

NAME READY STATUS RESTARTS AGE
questdb-0 1/1 Running 0 3s

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

&lt;/div&gt;



&lt;p&gt;💥 Perfect.&lt;/p&gt;

&lt;p&gt;Now that QuestDB is running, we'll want to port-forward to the service in-order to checkout the web UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl port-forward svc/questdb 8080:80

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

&lt;/div&gt;



&lt;p&gt;This setups a port-forward from our local machine into the Kubernetes cluster. We're specifying that the local port &lt;code&gt;8080&lt;/code&gt; should be forwarded to port &lt;code&gt;80&lt;/code&gt; our QuestDB service, &lt;code&gt;svc/questdb&lt;/code&gt;, inside the Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;As if by magic, you'll now be able to browse to &lt;code&gt;http://localhost:8080&lt;/code&gt; and see the QuestDB UI.&lt;/p&gt;

&lt;p&gt;Don't believe me? Try it 🤪&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Data
&lt;/h3&gt;

&lt;p&gt;One of the really cool features of QuestDB, besides its ridiculous performance with large data sets, is the support for InfluxDB line protocol. That means we can use Telegraf to write data to QuestDB within a matter of minutes.&lt;/p&gt;

&lt;p&gt;Assuming we have this &lt;code&gt;telegraf.conf&lt;/code&gt;, which is configured with input plugins to gather metrics from our host; notable the &lt;code&gt;cpu&lt;/code&gt;, &lt;code&gt;disk&lt;/code&gt;, and &lt;code&gt;mem&lt;/code&gt; plugins.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[agent]
  interval = "5s"

[[inputs.cpu]]
[[inputs.disk]]
[[inputs.mem]]

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

&lt;/div&gt;



&lt;p&gt;We can add Telegraf's generic &lt;code&gt;socket_writer&lt;/code&gt; output plugin to write our data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[[outputs.socket_writer]]
  address = "tcp://questdb:9009"
  data_format = "influx"

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

&lt;/div&gt;



&lt;p&gt;This output configuration specifies that we want Telegraf to use the line protocol data format and we want to write to a TCP socket on port &lt;code&gt;9009&lt;/code&gt;. This does require enabling one parameter within the &lt;code&gt;values.yaml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;First, lets take a look at our QuestDB service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 &amp;lt;none&amp;gt; 443/TCP 9d
questdb ClusterIP 10.105.188.157 &amp;lt;none&amp;gt; 80/TCP 52m

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

&lt;/div&gt;



&lt;p&gt;You can see that under &lt;code&gt;PORT(S)&lt;/code&gt; that it only lists port &lt;code&gt;80&lt;/code&gt;, which is the web UI. So we need to upgrade our QuestDB deployment with a change that enables the InfluxDB line protocol support.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt;&amp;gt; values.yaml
service:
  type: ClusterIP
  port: 80
  expose:
    influx:
      enabled: true
      port: 9009
EOF

helm upgrade -f values.yaml questdb ./questdb-kubernetes/charts/questdb

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

&lt;/div&gt;



&lt;p&gt;We've used &lt;code&gt;-f&lt;/code&gt; to specify our own values file, which is augmented / merged with the default one provided by the chart. Our &lt;code&gt;values.yaml&lt;/code&gt; adds the configuration we need to enable the InfluxDB support; which exposes our port on the QuestDB service. Cool, huh? Now if we check out the service again ...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 &amp;lt;none&amp;gt; 443/TCP 9d
questdb ClusterIP 10.105.188.157 &amp;lt;none&amp;gt; 80/TCP,9009/TCP 56m

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

&lt;/div&gt;



&lt;p&gt;We have port &lt;code&gt;9009&lt;/code&gt; 😀&lt;/p&gt;

&lt;p&gt;I'm not going to cover running Telegraf inside our Kubernetes cluster, at-least not in this article. I also maintain the chart that makes that easy-easy too; which you can find &lt;a href="https://github.com/influxdata/helm-charts"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy Peasy
&lt;/h3&gt;

&lt;p&gt;That is all it takes to get QuestDB running on a Kubernetes cluster using the official Helm chart and write some data to it with Telegraf.&lt;/p&gt;

&lt;p&gt;Of course, there's a variety of things to consider when making the leap to production; and I'll explore that more as I continue my journey with QuestDB. Until then, go check it out for yourself.&lt;/p&gt;

&lt;p&gt;Below is the deeper dive into the configuration available, if you're really keen.&lt;/p&gt;

&lt;p&gt;Have a great day!&lt;/p&gt;

&lt;h2&gt;
  
  
  Helm Chart Values Explanations
&lt;/h2&gt;

&lt;p&gt;Firstly, we have the option to override which image and tag to use for our QuestDB deployment. By default, it's configured to use &lt;code&gt;questdb/questdb&lt;/code&gt;, which is officially maintained by the QuestDB team. I would always recommend using officially maintained images, but if you're in an air gapped environment or have compliance teams to appease, you can override as needed. This may require using &lt;code&gt;imagePullSecrets&lt;/code&gt; to handle authentication for your private registry, which can also be provided. At the time of writing, the most recent version of QuestDB is &lt;code&gt;5.0.1&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;image:
  repository: questdb/questdb
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  #tag: "5.0.1"
imagePullSecrets: []

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

&lt;/div&gt;



&lt;p&gt;Next, we have &lt;code&gt;nameOverride&lt;/code&gt; and &lt;code&gt;fullnameOverride&lt;/code&gt;. Pretty much every chart offers these and they're strictly superficial. They allow configuring the name of your resources that are created as a result of the deployment. We're not going to use them today.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nameOverride: ""
fullnameOverride: ""

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

&lt;/div&gt;



&lt;p&gt;Now we get a little more interesting. &lt;code&gt;podAnnotations&lt;/code&gt; allow you to inject arbitrary annotations onto the pods. This can be useful for gathering metrics from your QuestDB deployment, primarily with Prometheus; which can use annotations for endpoint discovery.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;podAnnotations: {}

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

&lt;/div&gt;



&lt;p&gt;Now for some meaty security stuff. We can configure the user that our process within the containers run as, as well as the group ownership of the filesystems we need for storing our data. We also have the ability to be rather explicit about the capabilities our containers have at the kernel level. This is a &lt;strong&gt;HUGE&lt;/strong&gt; topic that needs to be handled gracefully, like a swan. &lt;strong&gt;HONK&lt;/strong&gt;. There was a great &lt;a href="https://www.youtube.com/watch?v=ibrDDaLD6Ks"&gt;TGIK&lt;/a&gt; with &lt;a href="https://twitter.com/mauilion"&gt;Duffie Cooley&lt;/a&gt; and &lt;a href="https://twitter.com/IanColdwater"&gt;Ian Coldwater&lt;/a&gt; recently that's worth watching, if Kubernetes security is something that tickles your fancy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000

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

&lt;/div&gt;



&lt;p&gt;OK. Now we get to configuring QuestDB itself. I'll be honest, I don't know much about this, yet. However, it is &lt;a href="https://questdb.io/docs/reference/configuration/server"&gt;documented&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;questdb:
  config: #{}
    enabled: true
    options:
      shared.worker.count: 2

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

&lt;/div&gt;



&lt;p&gt;Running QuestDB on Kubernetes is great ... but we also want to be able to consume it. We can use the &lt;code&gt;service&lt;/code&gt; block to enable other pods within our cluster to access QuestDB. QuestDB runs a web server, to provide its API and UI, on port &lt;code&gt;9000&lt;/code&gt;. However, it's quite common to expose that on the HTTP native port, &lt;code&gt;80&lt;/code&gt;, within Kubernetes. This means we can, from within the cluster, run &lt;code&gt;curl http://questdb&lt;/code&gt; and get what we expect.&lt;/p&gt;

&lt;p&gt;QuestDB also offers a few other ports, which allow for support of InfluxDB line protocol and PostgreSQL; on ports &lt;code&gt;9009&lt;/code&gt; and &lt;code&gt;8812&lt;/code&gt; respectfully. I actually forgot to add this to the Helm chart and it was contributed by &lt;a href="https://github.com/solidnerd"&gt;Niclas Mietz&lt;/a&gt;; so thank you! 😃&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service:
  type: ClusterIP
  port: 80
  expose:
    postgresql:
      enabled: false
      port: 8812
    influx:
      enabled: false
      port: 9009

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

&lt;/div&gt;



&lt;p&gt;You know what's more fun than accessing QuestDB internally within your cluster? Making it public ... ooooooh yeah.&lt;/p&gt;

&lt;p&gt;The Helm chart allows you to leverage Kubernetes Ingress controllers to expose your services to the big bad world. In fact, QuestDB being rather bold ... actually runs their &lt;a href="https://questdb.io/blog/2020/07/01/we-put-a-sql-database-on-the-internet"&gt;own public QuestDB&lt;/a&gt; that allows you to query 2.6 billion records ... that's wild. I don't think anyone's broken it ... yet 😂&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true"
# hosts:
# - host: chart-example.local
# paths: []
# tls: []
# # - secretName: chart-example-tls
# # hosts:
# # - chart-example.local

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

&lt;/div&gt;



&lt;p&gt;You know what good a database is without state / persistence? &lt;a href="https://www.youtube.com/watch?v=e0r2Apn7OKA"&gt;NOTHING&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can configure our QuestDB deployment to use persistence through Kubernetes primitives, PVC/PV's. Resizing these isn't as easy as one might hope, but it does continually get easier (especially since Kubernetes 1.17). So think carefully about how much space to provision.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;persistence:
  enabled: true
  #storageClass: "-"
  accessMode: ReadWriteOnce
  size: 50Gi

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

&lt;/div&gt;



&lt;p&gt;QuestDB does some magic stuff with the JVM to disable GC, which means that it can run on limited resources. You can use the &lt;code&gt;resources&lt;/code&gt; block to limit this explicitly. There's no guessing these numbers, I'm afraid. You'll need to run without these constraints and profile / monitor your pods to see what "normal" usage patterns look like. Then add a safety ceiling of (10|20|30)% and go with that. Best of luck ... resource constraints are tricky business.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi

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

&lt;/div&gt;



&lt;p&gt;Finally, the last block. Like every Helm chart before it and every Helm chart that will come after it, we provide the ability to encourage the scheduling of your QuestDB pods to the nodes that you wish, whether it be for storage class support or locality to your other producing or consuming pods; you can do whatever you need with the following three keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nodeSelector: {}
tolerations: []
affinity: {}

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/"&gt;Read more here&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Packet Metadata for SaltStack</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Mon, 03 Aug 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/rawkode/packet-metadata-for-saltstack-2d5i</link>
      <guid>https://dev.to/rawkode/packet-metadata-for-saltstack-2d5i</guid>
      <description>&lt;p&gt;In my last &lt;a href="//./saltstack-on-packet-with-pulumi"&gt;article&lt;/a&gt;, we spun up some bare metal compute on &lt;a href="https://www.packet.com"&gt;Packet&lt;/a&gt; with &lt;a href="https://www.pulumi.com"&gt;Pulumi&lt;/a&gt; and installed &lt;a href="https://www.saltstack.com"&gt;SaltStack&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order to use SaltStack to provision our workloads on our servers, we need a way to identify which machines should be provisioned with what workload. SaltStack uses &lt;a href="https://docs.saltstack.com/en/latest/topics/grains/"&gt;Grains&lt;/a&gt; to do this ... and there's a metadata grain that can read metadata from cloud providers; unfortunately, it doesn't support Packet.&lt;/p&gt;

&lt;p&gt;Drats ☹️&lt;/p&gt;

&lt;p&gt;Happy news though, SaltStack is rather extensible; as long as you don't mind getting your hands a little dirty with Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing a Custom Grain
&lt;/h2&gt;

&lt;p&gt;Writing a SaltStack grain module is SUPER easy. Lets take a look at the simplest implementation I can put together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test():
    return dict(test={
        "name": "David",
        "age": "18",
    })

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

&lt;/div&gt;



&lt;p&gt;Yeah, yeah. I know I'm not 18 anymore. Shush.&lt;/p&gt;

&lt;p&gt;Grain modules are Python functions that return key value pairs. This code above returns a grain named "test" with the key/value pairs &lt;code&gt;name = David&lt;/code&gt; and &lt;code&gt;age = 18&lt;/code&gt;. This means we can run &lt;code&gt;salt minion-1 grains.item test&lt;/code&gt; and we'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;minion-1:
    ----------
    test:
        ----------
        name:
            David
        age:
            18

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

&lt;/div&gt;



&lt;p&gt;Of course, we don't want to return hared coded key values! We want to return information about our servers from Packet's &lt;a href="https://www.packet.com/developers/docs/servers/key-features/metadata/"&gt;metadata API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code to handle this isn't particularly complicated. In-fact, performing a HTTP request in Python is really simple 😀&lt;/p&gt;

&lt;p&gt;Lets take a look.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import logging
import salt.utils.http as http

# Setup logging
log = logging.getLogger( __name__ )

# metadata server information
HOST = "https://metadata.packet.net/metadata"

def packet_metadata():
    response = http.query(HOST)
    metadata = json.loads(response["body"])

    log.error(metadata)

    grains = {}
    grains["id"] = metadata["id"]
    grains["iqn"] = metadata["iqn"]
    grains["plan"] = metadata["plan"]
    grains["class"] = metadata["class"]
    grains["facility"] = metadata["facility"]

    grains["tags"] = metadata["tags"]

    return dict(packet_metadata=grains)

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

&lt;/div&gt;



&lt;p&gt;The important lines here are these three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HOST = "https://metadata.packet.net/metadata"

response = http.query(HOST)
metadata = json.loads(response["body"])

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

&lt;/div&gt;



&lt;p&gt;We first query the metadata API endpoint, defined by the variable &lt;code&gt;HOST&lt;/code&gt;. We then decode the body of the response into a Python dict, using &lt;code&gt;json.loads&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This gives us access to every bit of metadata returned by the Packet metadata API. That looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "id": "c5ce85c5-1eef-4581-90b6-88a91e47e207",
  "hostname": "master-1",
  "iqn": "iqn.2020-08.net.packet:device.c5ce85c5",
  "operating_system": {
    "slug": "debian_9",
    "distro": "debian",
    "version": "9",
    "license_activation": {
      "state": "unlicensed"
    },
    "image_tag": "b32a1f31b127ef631d6ae31af9c6d8b69dcaa9e9"
  },
  "plan": "c2.medium.x86",
  "class": "c2.medium.x86",
  "facility": "ams1",
  "private_subnets": ["10.0.0.0/8"],
  "tags": ["role/salt-master"],
  "ssh_keys": [
    "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGf0w9b+lPcZhsNHU8Sw5hJPBhpNICTNkjlBz9jxtLbWNGvHTE1lBeXU5VA2/7cuYw48apHmMURHFtK5AZx3srg="
  ],
  "storage": {
    "disks": [
      {
        "device": "/dev/sdd",
        "wipeTable": true,
        "partitions": [
          {
            "label": "BIOS",
            "number": 1,
            "size": "512M"
          },
          {
            "label": "SWAP",
            "number": 2,
            "size": "3993600"
          },
          {
            "label": "ROOT",
            "number": 3,
            "size": 0
          }
        ]
      }
    ],
    "filesystems": [
      {
        "mount": {
          "device": "/dev/sdd1",
          "format": "vfat",
          "point": "/boot/efi",
          "create": {
            "options": ["32", "-n", "EFI"]
          }
        }
      },
      {
        "mount": {
          "device": "/dev/sdd3",
          "format": "ext4",
          "point": "/",
          "create": {
            "options": ["-L", "ROOT"]
          }
        }
      },
      {
        "mount": {
          "device": "/dev/sdd2",
          "format": "swap",
          "point": "none",
          "create": {
            "options": ["-L", "SWAP"]
          }
        }
      }
    ]
  },
  "network": {
    "bonding": {
      "mode": 4,
      "link_aggregation": "bonded",
      "mac": "50:6b:4b:b4:a9:3a"
    },
    "interfaces": [
      {
        "name": "eth0",
        "mac": "50:6b:4b:b4:a9:3a",
        "bond": "bond0"
      },
      {
        "name": "eth1",
        "mac": "50:6b:4b:b4:a9:3b",
        "bond": "bond0"
      }
    ],
    "addresses": [
      {
        "id": "5d28837b-29c5-4505-bb05-930fd3760bac",
        "address_family": 4,
        "netmask": "255.255.255.252",
        "created_at": "2020-08-03T14:07:50Z",
        "public": true,
        "cidr": 30,
        "management": true,
        "enabled": true,
        "network": "147.75.84.128",
        "address": "147.75.84.130",
        "gateway": "147.75.84.129",
        "parent_block": {
          "network": "147.75.84.128",
          "netmask": "255.255.255.252",
          "cidr": 30,
          "href": "/ips/7a30c2bf-f0e5-402c-b0c0-b8ab03359e63"
        }
      },
      {
        "id": "937552c6-cf1a-474d-9866-9fb1e0525503",
        "address_family": 4,
        "netmask": "255.255.255.254",
        "created_at": "2020-08-03T14:07:49Z",
        "public": false,
        "cidr": 31,
        "management": true,
        "enabled": true,
        "network": "10.80.76.4",
        "address": "10.80.76.5",
        "gateway": "10.80.76.4",
        "parent_block": {
          "network": "10.80.76.0",
          "netmask": "255.255.255.128",
          "cidr": 25,
          "href": "/ips/8f8cd919-165a-4e62-b461-af7c15a25ec4"
        }
      }
    ]
  },
  "customdata": {},
  "specs": {
    "cpus": [
      {
        "count": 1,
        "type": "AMD EPYC 7401P 24-Core Processor @ 2.0GHz"
      }
    ],
    "memory": {
      "total": "64GB"
    },
    "drives": [
      {
        "count": 2,
        "size": "120GB",
        "type": "SSD",
        "category": "boot"
      },
      {
        "count": 2,
        "size": "480GB",
        "type": "SSD",
        "category": "storage"
      }
    ],
    "nics": [
      {
        "count": 2,
        "type": "10Gbps"
      }
    ],
    "features": {}
  },
  "switch_short_id": "f8dd5e3f",
  "volumes": [],
  "api_url": "https://metadata.packet.net",
  "phone_home_url": "http://tinkerbell.ams1.packet.net/phone-home",
  "user_state_url": "http://tinkerbell.ams1.packet.net/events"
}

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

&lt;/div&gt;



&lt;p&gt;I decided not to make all of this available within the grains system, as only a few data points make sense for scheduling workloads. Hence, I cherry pick out the attributes I want for the next demo. You can pick and choose whatever you want too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grains = {}
grains["id"] = metadata["id"]
grains["iqn"] = metadata["iqn"]
grains["plan"] = metadata["plan"]
grains["class"] = metadata["class"]
grains["facility"] = metadata["facility"]

grains["tags"] = metadata["tags"]

return dict(packet_metadata=grains)

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Provisioning the Custom Grain
&lt;/h2&gt;

&lt;p&gt;Now that we have a custom grain, we need to update our Pulumi code to install this on our Salt master.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NB&lt;/strong&gt; : We &lt;strong&gt;only&lt;/strong&gt; need to make this grain available on our Salt master, as the Salt master takes responsibility for syncing custom grains to the minions.&lt;/p&gt;

&lt;p&gt;I've updated our &lt;a href="https://gitlab.com/rawkode/packet-examples/-/blob/master/pulumi-saltstack/src/salt-master/user-data.sh"&gt;user-data.sh&lt;/a&gt; to create the directory we need and added the mustache template syntax that allows us to inject the Python script. We use &lt;code&gt;&amp;amp;&lt;/code&gt; before the variable name to request that mustache doesn't escape our quotes to HTML entities ... I only learnt that today 😂&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p /srv/salt/_grains

cat &amp;lt;&amp;lt;EOF &amp;gt;/srv/salt/_grains/packet_metadata.py
{{ &amp;amp;PACKET_METADATA_PY }}
EOF

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

&lt;/div&gt;



&lt;p&gt;Next up, we provide the Python script at render time and provide some tags for our servers when we create them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const pythonPacketMetadataGrain = fs
  .readFileSync(path.join(__dirname, "..", "salt", "packet_metadata.py"))
  .toString();

const saltMaster = new Device(`master-${name}`, {
  // ... code omitted for brevity
  userData: mustache.render(bootstrapString, {
    PACKET_METADATA_PY: pythonPacketMetadataGrain,
  }),
  // Add tags to this server
  tags: ["role/salt-master"],
});

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Syncing Custom Grains
&lt;/h3&gt;

&lt;p&gt;Finally, we need to tell our Salt master to sync the grains to our minions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt "*" saltutil.grains_sync

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

&lt;/div&gt;



&lt;p&gt;You can now confirm the custom grain is working with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@master-1:~# salt "*" grains.item packet_metadata
minion-1:
    ----------
    packet_metadata:
        ----------
        class:
            c2.medium.x86
        facility:
            ams1
        id:
            ab0bc2ba-557b-4d99-a1eb-0beec02adff2
        iqn:
            iqn.2020-08.net.packet:device.ab0bc2ba
        plan:
            c2.medium.x86
        tags:
            - role/salt-minion
master-1:
    ----------
    packet_metadata:
        ----------
        class:
            c2.medium.x86
        facility:
            ams1
        id:
            97ce9196-077d-4ce9-82a5-d58bf59d0dbc
        iqn:
            iqn.2020-08.net.packet:device.97ce9196
        plan:
            c2.medium.x86
        tags:
            - role/salt-master

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

&lt;/div&gt;



&lt;p&gt;That's it! Next time we'll take a look at using our tags to provision and schedule our workloads.&lt;/p&gt;

&lt;p&gt;See you then.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SaltStack on Packet with Pulumi</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Thu, 30 Jul 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/rawkode/saltstack-on-packet-with-pulumi-c7p</link>
      <guid>https://dev.to/rawkode/saltstack-on-packet-with-pulumi-c7p</guid>
      <description>&lt;p&gt;&lt;strong&gt;WIP: You probably don't want to read this yet 😂&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I joined &lt;a href="https://packet.com"&gt;Packet&lt;/a&gt; on July 27th (4 days ago, at the time of writing) as a Senior Tech Evangelist. This is exciting! I get to work for a Cloud company that specialises in bare metal compute. So what should I do first?&lt;/p&gt;

&lt;p&gt;I'm going to launch my favourite configuration management software with my favourite infrastructure as code software on my new favourite cloud provider. Sweet, right? 🍭🍬&lt;/p&gt;

&lt;p&gt;If you don't want to read the walk through, the &lt;a href="https://gitlab.com/rawkode/packet-examples/-/tree/master/pulumi-saltstack"&gt;code is available here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SaltStack?
&lt;/h2&gt;

&lt;p&gt;Reasons&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Pulumi?
&lt;/h2&gt;

&lt;p&gt;Reasons&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1. Pulumi
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pulumi.com"&gt;Pulumi&lt;/a&gt; is a Infrastructure as Code (IaaC) tool that allows you to describe your infrastructure, much like &lt;a href="https://terraform.io"&gt;Terraform&lt;/a&gt;. Unlike Terraform, Pulumi doesn't impose a specific DSL, HCL, on you; and instead, you can use your programmaing language of choice ... provided it's supported.&lt;/p&gt;

&lt;p&gt;At the time of writing, Pulumi supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C#&lt;/li&gt;
&lt;li&gt;F#&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;VisualBasic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I know, I know. I'm disappinted &lt;a href="https://www.rust-lang.org/"&gt;Rust&lt;/a&gt; isn't there either. &lt;a href="https://github.com/pulumi/pulumi/issues/3622"&gt;Maybe one day&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Stack
&lt;/h3&gt;

&lt;p&gt;So to create our stack, we need to generate a new Pulumi project. For this example, we'll use the Pulumi "local" login, which stores the statefile on our local disk. The statefile is very similar to Terraform state. It is needed to build an execution plan for our &lt;code&gt;apply&lt;/code&gt; commands. Using local will suffice today, but you should investigate using &lt;a href="https://www.pulumi.com/docs/intro/concepts/state/"&gt;alternative options&lt;/a&gt; for production deployments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pulumi login --local

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

&lt;/div&gt;



&lt;p&gt;We're going to use the TypeScript template to get started. Unfortunately, there isn't a template for all supported languages, but templates do exist for the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packet-go # A minimal Packet.net Go Pulumi program
packet-javascript # A minimal Packet.net JavaScript Pulumi program
packet-python # A minimal Packet.net Python Pulumi program
packet-typescript # A minimal Packet.net TypeScript Pulumi program

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

&lt;/div&gt;



&lt;p&gt;If you want to use one of the dotNet languages, you can use a generic template; then add the Packet provider manually. Generic templates for dotNet are called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;csharp # A minimal C# Pulumi program
fsharp # A minimal F# Pulumi program
visualbasic # A minimal VB.NET Pulumi program

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

&lt;/div&gt;



&lt;p&gt;To create our project from the TypeScript template, let's run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pulumi new packet-typescript

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

&lt;/div&gt;



&lt;p&gt;You'll be walked through a couple of questions to create your stack, after which you'll have a directory that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drwxr-xr-x - rawkode 30 Jul 18:07 node_modules
.rw------- 286 rawkode 30 Jul 18:06 index.ts
.rw-r--r-- 28k rawkode 30 Jul 18:07 package-lock.json
.rw------- 201 rawkode 30 Jul 18:06 package.json
.rw-r--r-- 85 rawkode 30 Jul 18:07 Pulumi.dev.yaml
.rw------- 100 rawkode 30 Jul 18:06 Pulumi.yaml
.rw------- 438 rawkode 30 Jul 18:06 tsconfig.json

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

&lt;/div&gt;



&lt;p&gt;If we take a look inside of &lt;code&gt;index.ts&lt;/code&gt;, we'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as pulumi from "@pulumi/pulumi";
import * as packet from "@pulumi/packet";

// Create a Packet resource (Project)
const project = new packet.Project("my-test-project", {
  name: "My Test Project",
});

// Export the name of the project
export const projectName = project.name;

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

&lt;/div&gt;



&lt;p&gt;This TypeScript uses the Pulumi SDKs to provide a nice wrapper around the Packet API. Hopefully it's pretty self-explanitory; you can see that it creates a new project and exports it by name.&lt;/p&gt;

&lt;p&gt;Exports are similar to Terraform outputs. We use an export when we want to make some attribute from our stack available outside of Pulumi. Pulumi provides the &lt;code&gt;pulumi stack output&lt;/code&gt; command, which displays these exports.&lt;/p&gt;

&lt;p&gt;We'll use these later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleaning Up the Stack
&lt;/h3&gt;

&lt;p&gt;This step is completely subjective, but I don't like my code just chilling in the top level directory with all the other stuff. What is this, Go? 😏&lt;/p&gt;

&lt;p&gt;Fortunately, we can append &lt;code&gt;main: src/index.ts&lt;/code&gt; to the &lt;code&gt;Pulumi.yaml&lt;/code&gt; file, which tells Pulumi our entyrypoint for this stack lives somewhere else. I'm going to use a &lt;code&gt;src&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir src
mv index.ts src/
echo "main: src/index.ts" &amp;gt;&amp;gt; Pulumi.yaml

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a "Platform"
&lt;/h3&gt;

&lt;p&gt;I like to create a &lt;code&gt;Platform&lt;/code&gt; object / type / class that can be used to pass around the Pulumi configuration and some other common types that my Pulumi projects often need. This saves my function signatures getting too gnarly as we add new components to our stacks.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Platform&lt;/code&gt; object I'm using for this is pretty trivial. It loads the Pulumi configuration and stores our Packet Project, which means we can pass around &lt;code&gt;Platform&lt;/code&gt; to other functions and it's a single argument, rather than many.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ./src/platform.ts
import { Config } from "@pulumi/pulumi";
import { Project } from "@pulumi/packet";

export type Platform = {
  project: Project;
  config: Config;
};

export const getPlatform = (project: Project): Platform =&amp;gt; {
  return {
    project,
    config: new Config(),
  };
};

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

&lt;/div&gt;



&lt;p&gt;Now we can update our &lt;code&gt;./src/index.ts&lt;/code&gt; to look like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as packet from "@pulumi/packet";
import { getPlatform } from "./platform";

const project = new packet.Project("pulumi-saltstack-example", {
  name: "pulumi-saltstack-example",
});

const platform = getPlatform(project);

export const projectName = platform.project.name;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating the SaltMaster
&lt;/h3&gt;

&lt;p&gt;Now we want to create the Salt Master server. For this, I create a new directory with an &lt;code&gt;index.ts&lt;/code&gt; that exports a function called &lt;code&gt;createSaltMaster&lt;/code&gt;; which I can consume in our &lt;code&gt;./src/index.ts&lt;/code&gt;, much like we did with our &lt;code&gt;Platform&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the complete file, but I'll run through each part seperately too; don't worry! I'm not going to explain the imports, I'll do that as we go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ./src/salt-master/index.ts
import {
  Device,
  IpAddressTypes,
  OperatingSystems,
  Plans,
  Facilities,
  BillingCycles,
} from "@pulumi/packet";
import { Platform } from "../platform";
import * as fs from "fs";
import * as path from "path";
import * as mustache from "mustache";

export type SaltMaster = {
  device: Device;
};

export const createSaltMaster = (
  platform: Platform,
  name: string
): SaltMaster =&amp;gt; {
  // While we're not interpolating anything in this script atm,
  // might as well leave this code in for the time being; as
  // we probably will shortly.
  const bootstrapString = fs
    .readFileSync(path.join(__dirname, "./user-data.sh"))
    .toString();

  const bootstrapScript = mustache.render(bootstrapString, {});

  const saltMaster = new Device(`master-${name}`, {
    hostname: name,
    plan: Plans.C1LargeARM,
    facilities: [Facilities.AMS1],
    operatingSystem: OperatingSystems.Debian9,
    billingCycle: BillingCycles.Hourly,
    ipAddresses: [
      { type: IpAddressTypes.PrivateIPv4, cidr: 31 },
      {
        type: IpAddressTypes.PublicIPv4,
      },
    ],
    projectId: platform.project.id,
    userData: bootstrapScript,
  });

  return {
    device: saltMaster,
  };
};

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  SaltMaster Return Type
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export type SaltMaster = {
  device: Device;
};

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

&lt;/div&gt;



&lt;p&gt;Because this is TypeScript, we want to be very explicit about the return types within our code. This allows us to catch errors before we ever run our Pulumi stack. As we're using &lt;code&gt;createSaltMaster&lt;/code&gt; function to create our SaltMaster, we want that function to return a type with the resources we create.&lt;/p&gt;

&lt;p&gt;While our function only returns a &lt;code&gt;Device&lt;/code&gt;, it's still nice to encapsulate that in a named type that allows for our function to evolve over time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Platform } from "../platform";

export const createSaltMaster = (
  platform: Platform,
  name: string
): SaltMaster =&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;This is the function signature for &lt;code&gt;createSaltMaster&lt;/code&gt;. You can see our return type is the type we just created, &lt;code&gt;SaltMaster&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our function also takes a couple of parameters, namely &lt;code&gt;platform&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;. The platform is our &lt;code&gt;Platform&lt;/code&gt; object with our Pulumi configuration and the Packet Project, so we also need to import it. The &lt;code&gt;name&lt;/code&gt; allows us to give our SaltMaster a name when we create the device on Packet. We could hardcode this inside the function as &lt;code&gt;salt-master&lt;/code&gt;, but then we can't use &lt;code&gt;createSaltMaster&lt;/code&gt; more than once for a highly available set up in a later tutorial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as fs from "fs";
import * as path from "path";
import * as mustache from "mustache";

const bootstrapString = fs
  .readFileSync(path.join(__dirname, "./user-data.sh"))
  .toString();

const bootstrapScript = mustache.render(bootstrapString, {});

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

&lt;/div&gt;



&lt;p&gt;I know what you're thinking ... but trust me. As Pulumi allows us to use a programming language to describe our infrastructure, we also have access to that programming languages entire eco-system of libraries. As such, if I want to template some user data for a server ... say, to provision and install SaltStack ... I can use a popular templating tool, such as mustache, from npm 😉&lt;/p&gt;

&lt;p&gt;My user data for the Salt Master looks like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ./src/salt-master/user-data.sh
#!/usr/bin/env sh
apt update
DEBIAN_FRONTEND=noninteractive apt install -y python-zmq python-systemd python-tornado salt-common salt-master

LOCAL_IPv4=$(ip addr | grep -E -o '10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')

cat &amp;lt;&amp;lt;EOF &amp;gt;/etc/salt/master.d/listen-interface.conf
interface: ${LOCAL_IPv4}
EOF

systemctl restart salt-master

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

&lt;/div&gt;



&lt;p&gt;Lastly, we need to create the device with the Packet API. We use the Pulumi SDK to do so. I'm explictly importing the required types that I need to use as much of the type system as I can.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  Device,
  IpAddressTypes,
  OperatingSystems,
  Plans,
  Facilities,
  BillingCycles,
} from "@pulumi/packet";

const saltMaster = new Device(`master-${name}`, {
  hostname: name,
  plan: Plans.C1LargeARM,
  facilities: [Facilities.AMS1],
  operatingSystem: OperatingSystems.Debian9,
  billingCycle: BillingCycles.Hourly,
  ipAddresses: [
    { type: IpAddressTypes.PrivateIPv4, cidr: 31 },
    {
      type: IpAddressTypes.PublicIPv4,
    },
  ],
  projectId: platform.project.id,
  userData: bootstrapScript,
});

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

&lt;/div&gt;



&lt;p&gt;Next up, we can call our &lt;code&gt;createSaltMaster&lt;/code&gt; function from &lt;code&gt;./src/index.ts&lt;/code&gt; and we'll have a server with the correct user data for running our &lt;code&gt;salt-master&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const saltMaster = createSaltMaster(platform, "master-1");
export const saltMasterPublicIp = saltMaster.device.accessPublicIpv4;

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

&lt;/div&gt;



&lt;p&gt;We're going to export it's public IPv4 address; so that we can access it easily later and SSH into the machine later.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automate Google Calendar Invitations with Slack and Pipedream</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Wed, 15 Jul 2020 15:01:39 +0000</pubDate>
      <link>https://dev.to/rawkode/automate-google-calendar-invitations-with-slack-and-pipedream-53bp</link>
      <guid>https://dev.to/rawkode/automate-google-calendar-invitations-with-slack-and-pipedream-53bp</guid>
      <description>&lt;p&gt;When I was working at InfluxData, I put together a programme that aimed to solve a common business challenge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we encourage and enable cross-functional collaboration within a fast moving organisation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Having spent many years working and contributing to open source communities, I've seen many governance and collaboration models used across various successful open source projects; but the one that continues to impress me to this day is the Special Interest Groups (SIGs) model used by Kubernetes.&lt;/p&gt;

&lt;p&gt;I was curious what would happen if we adopted SIGs internally to provide guidance around our organisations involvement, support, and enable of our technology within external communities. That is to say that InfluxData had people across multiple disciplines that wished to collaborate on the use of our product within OpenTracing, Kubernetes, Machine Learning, and many other communities and ecosystems; but until then had no structured way to drive that collaboration forward.&lt;/p&gt;

&lt;p&gt;I'm not writing this article to talk about the progress of that programme, perhaps I'll do that later. What I wanted to write about was one of the challenges we had and how we solved it with a fantastic piece of open source software: &lt;a href="https://www.pipedream.com"&gt;Pipedream&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When I put this programme together, one of my goals was for each SIG to be inclusive. This meant that I was keen to avoid any one person controlling the calendar invitations, deciding whom they seen fit to join each SIG. Instead, I wanted each SIG to have a doors open policy that invited anyone and everyone to join their initiatives.&lt;/p&gt;

&lt;p&gt;Google Calendar provides "Team Calendars" to help achieve this. The concept is simple, but sadly the implementation is painful. While I was able to successfully create the shared calendar and pass around a link, the link didn't seem to work for everyone and was cumbersome, at best, to consume.&lt;/p&gt;

&lt;p&gt;More importantly, &lt;strong&gt;there's no discovery of these events, you need to be told to look for them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This led to the first few meetings having next to no participation, other than the core people that expressed an interest in the groups upfront.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;So I got my thinking hat on. One of the mandates of the SIG programme was that every SIG &lt;strong&gt;MUST&lt;/strong&gt; have a Slack channel, where the chair will share updates after each meeting.&lt;/p&gt;

&lt;p&gt;What if we could consider "membership" of the Slack channel as an indicator that they probably want to be aware of the events; hell, even auto-invite them? I kept my thinking hat on 🎩&lt;/p&gt;

&lt;p&gt;The goal is now this:&lt;/p&gt;

&lt;p&gt;Whenever anyone joins one of our &lt;code&gt;#sig-channels&lt;/code&gt; on Slack, we automatically update the Google Calendar event (which still lives in the team calendar) with their email address.&lt;/p&gt;

&lt;p&gt;Lets do this!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Implementation
&lt;/h2&gt;

&lt;p&gt;I like to avoid writing code or deploying anything as much as possible. NoCode and LowCode solutions are very exciting to me; because they're enablers for everyone, no matter what your constraints are. My biggest constraint is time, not technical knowledge; but NoCode and LowCode are great enablers, or gateways, for non-technical folk. I love these solutions.&lt;/p&gt;

&lt;p&gt;So I decided to utilise one of these solutions to provide the "plumbing" of this auto-inviter, hopefully allowing me to deliver a solution to my problem without writing or deploying any code or containers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipedream
&lt;/h3&gt;

&lt;p&gt;You can read more about Pipedream at their &lt;a href="https://www.pipedream.com"&gt;website&lt;/a&gt;, but I'll share the first paragraph from their docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pipedream is an integration platform for developers to build and run workflows that integrate apps, data, and APIs — no servers or infrastructure to manage!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Develop any workflow, based on any trigger.&lt;/li&gt;
&lt;li&gt;Workflows are code, which you can run for free.&lt;/li&gt;
&lt;li&gt;No server or cloud resources to manage.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;It ticked all the boxes I had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support Slack and Google Calendar as sources / sinks&lt;/li&gt;
&lt;li&gt;OpenSource&lt;/li&gt;
&lt;li&gt;NoCode / LowCode&lt;/li&gt;
&lt;li&gt;SaaS (I didn't want to deploy anything)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It took me 30 minutes to implement this, let me show you how.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1. Connect Your Accounts
&lt;/h3&gt;

&lt;p&gt;I'm not going to cover this really, but Pipedream makes it very easy to connect your Pipedream account with your other services.&lt;/p&gt;

&lt;p&gt;For this tutorial, you need to connect Twitter and Slack using the built-in OAuth prompts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2. Prepare the Webhook
&lt;/h3&gt;

&lt;p&gt;I decided that I wanted to use Slack's webhooks / events API to consume events from Slack. It's the easiest way to get started and it works really well in a consume and emit / event driven workflow.&lt;/p&gt;

&lt;p&gt;Pipedream provides HTTP endpoints that can receive arbitrary payloads and you can build your workflow around these with ease.&lt;/p&gt;

&lt;p&gt;So I created a webhook using the Pipedream UI and I got a URL like &lt;code&gt;https://randomID.m.pipedream.net&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://docs.pipedream.com/workflows/steps/triggers/#webhook"&gt;see how to do this yourself, on their docs&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pro Tip
&lt;/h4&gt;

&lt;p&gt;Pipedream allows you to see the payloads that have hit your endpoint. I suggest hooking up your source ASAP and building up a history of payloads to see what you need to handle.&lt;/p&gt;

&lt;p&gt;Pipedream also provides autocomplete based on previous payloads when working with future steps in the workflow.&lt;/p&gt;

&lt;p&gt;Ridiculous, right?&lt;/p&gt;

&lt;p&gt;🥰🥰🥰&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3. Slack Challenge
&lt;/h3&gt;

&lt;p&gt;When you add a new receiver for Slack events, it first sends a challenge. You need to be able to respond to this correctly with their challenge string.&lt;/p&gt;

&lt;p&gt;Pipedream allows us to add arbitrary JavaScript to handle payloads through a "NodeJS Step". The code I used was really simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;$respond&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&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;We check if the payload contains a &lt;code&gt;challenge&lt;/code&gt; parameter and we respond with it.&lt;/p&gt;

&lt;p&gt;Next!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4. Slack Channel Lookup
&lt;/h3&gt;

&lt;p&gt;I configured the Slack events integration to only send the join channel events. Part of this payload is a channel identifier, but not the channel name. So we need to query the Slack API to get the actual name of the channel.&lt;/p&gt;

&lt;p&gt;We use the NodeJS Step again to build up a config object to send to Slack with &lt;a href="https://github.com/axios/axios"&gt;axios&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pipedream provides the authentication we need through the &lt;code&gt;auths&lt;/code&gt; object that is available after we complete the OAuth connection from Step 1.&lt;/p&gt;

&lt;p&gt;We need to configure a &lt;code&gt;param&lt;/code&gt; for this step, which we can do through the GUI. You add a param called &lt;code&gt;channel&lt;/code&gt; which using the shiny autocomplete dropdown we can set to &lt;code&gt;event.body.event.channel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;event.body&lt;/code&gt; is the payload we receive from Slack, which contains &lt;code&gt;event.channel&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// See the API docs here: https://api.slack.com/methods/channels.info&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;include_locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;include_locale&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;https://slack.com/api/conversations.info?channel=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;&amp;amp;include_locale=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;include_locale&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="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;auths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth_access_token&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pipedreamhq/platform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&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;$end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Couldn't fetch Channel information&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4. User Lookup
&lt;/h3&gt;

&lt;p&gt;Much like the channel lookup, we also need to lookup the user information. We can't add the Slack ID to a Google Calendar invite, we need their email address.&lt;/p&gt;

&lt;p&gt;This time we configure a param called &lt;code&gt;user&lt;/code&gt;, which comes from &lt;code&gt;event.body.event.user&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//See the API docs here: https://api.slack.com/methods/users.info&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;include_locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;include_locale&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;https://slack.com/api/users.info&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;auths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth_access_token&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;params&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="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pipedreamhq/platform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&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;$end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to get user information&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;real_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;h3&gt;
  
  
  Step 5. Add to Google Calendar Event
&lt;/h3&gt;

&lt;p&gt;Finally, we want to add them to the invite! Unfortunately you need to hardcode some values here, one identifier for each event. I'm sure there's a way to do this programmatically with the Google Calendar API, but I've not worked that out yet.&lt;/p&gt;

&lt;p&gt;If the channel isn't one of the expected channels we have an event for; we exit early with &lt;code&gt;$end()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For channels we do understand, we add the email address to the event. This is idempotent, so we don't need to check if the user already exists on the invite.&lt;/p&gt;

&lt;p&gt;Pipedream allows us to fetch variables from the previous steps, which we use to get the users name and email address. Neat, huh?&lt;/p&gt;

&lt;p&gt;This looks like &lt;code&gt;steps.slack_get_user_info.userEmail&lt;/code&gt;, where &lt;code&gt;slack_get_user_info&lt;/code&gt; is the name of the previous step, and &lt;code&gt;userEmail&lt;/code&gt; is the variable I "exposed" with the &lt;code&gt;this.userEmail =&lt;/code&gt; syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slack_get_channel_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sig-kubernetes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EventID from Google Calendar&lt;/span&gt;&lt;span class="dl"&gt;"&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sig-opentelemetry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EventID from Google Calendar&lt;/span&gt;&lt;span class="dl"&gt;"&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sig-ml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EventID from Google Calendar&lt;/span&gt;&lt;span class="dl"&gt;"&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="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;$end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not a SIG channel.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;calendarId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pipedreamhq/platform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;https://www.googleapis.com/calendar/v3/calendars/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;/events/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;eventId&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="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;auths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;google_calendar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth_access_token&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="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&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;newAttendee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slack_get_user_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slack_get_user_info&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="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attendees&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attendees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newAttendee&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attendees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;newAttendee&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pipedreamhq/platform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;https://www.googleapis.com/calendar/v3/calendars/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;/events/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;eventId&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="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;auths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;google_calendar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oauth_access_token&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="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>node</category>
      <category>javascript</category>
      <category>pipedream</category>
      <category>slack</category>
    </item>
    <item>
      <title>Contributing to nixpkgs</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Wed, 15 Jul 2020 10:49:26 +0000</pubDate>
      <link>https://dev.to/rawkode/contributing-to-nixpkgs-1j1a</link>
      <guid>https://dev.to/rawkode/contributing-to-nixpkgs-1j1a</guid>
      <description>&lt;p&gt;I've been using &lt;a href="https://nixos.org/"&gt;NixOS&lt;/a&gt;, on and off, for around 2 years now. It's got its challenges, which&lt;br&gt;
usually means I switch back to Arch after a few weeks; but not this time ... I'm not switching back.&lt;/p&gt;

&lt;p&gt;As I stumble my way through the trials and tribulations of Nix, NixOS, and nixpkgs; I'll document&lt;br&gt;
my path so that others can learn from my misery. Starting right now ...&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Nix / NixOS?
&lt;/h2&gt;

&lt;p&gt;This is from the NixOS website:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nix is a powerful package manager for Linux and other Unix systems that makes package management reliable and reproducible. Share&lt;br&gt;
your development and build environments across different machines.&lt;/p&gt;

&lt;p&gt;NixOS is a Linux distribution with a unique approach to package and configuration management. Built on top of the Nix package&lt;br&gt;
manager, it is completely declarative, makes upgrading systems reliable, and has many other advantages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Contributing a GNOME Extension to nixpkgs
&lt;/h2&gt;

&lt;p&gt;In this article, I am going to walk you through the steps I've taken to contribute a GNOME Extension&lt;br&gt;
to the nixpkgs repository. The extension is one that I just CAN'T live without, the&lt;br&gt;
&lt;a href="https://github.com/maoschanz/emoji-selector-for-gnome"&gt;emoji selector&lt;/a&gt; 😹🌟🥰&lt;/p&gt;

&lt;p&gt;This wasn't my first contribution to &lt;a href="https://github.com/NixOS/nixpkgs/pulls?q=is%3Apr+author%3Arawkode+is%3Aclosed"&gt;nixpkgs&lt;/a&gt;, I've&lt;br&gt;
sent 16 pull requests to nixpkgs since August 2018 ... and I still have no idea what I'm doing 😂&lt;/p&gt;

&lt;p&gt;OK. That's a lie, I know enough to be dangerous, but not enough to be smart.&lt;/p&gt;

&lt;p&gt;Lets try this, one step at a time.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1. Clone the Repository
&lt;/h3&gt;

&lt;p&gt;Not much needs said about this, right?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;git clone https://github.com/NixOS/nixpkgs
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2. Find Something Similiar
&lt;/h3&gt;

&lt;p&gt;I'm not a huge fan of reinventing the wheel. I'm certainly not going to type a bunch of Nix code&lt;br&gt;
that shares about 95% of it's logic with many other packages already contributed to the nixpkgs repository.&lt;/p&gt;

&lt;p&gt;Fortunately for us, this repository is segmented really well.&lt;/p&gt;

&lt;p&gt;First, all packages live under &lt;code&gt;./pkgs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;drwxr-xr-x - rawkode 11 Jul 13:34 -- applications
drwxr-xr-x - rawkode 11 Jul 13:34 -- build-support
drwxr-xr-x - rawkode 11 Jul 13:34 -- common-updater
drwxr-xr-x - rawkode 11 Jul 13:34 -- data
drwxr-xr-x - rawkode 11 Jul 13:34 -- desktops
drwxr-xr-x - rawkode 11 Jul 13:34 -- development
drwxr-xr-x - rawkode 11 Jul 13:34 -- games
drwxr-xr-x - rawkode 11 Jul 13:34 -- misc
drwxr-xr-x - rawkode 11 Jul 13:34 -- os-specific
drwxr-xr-x - rawkode 11 Jul 13:34 -- servers
drwxr-xr-x - rawkode 11 Jul 13:34 -- shells
drwxr-xr-x - rawkode 11 Jul 13:34 -- stdenv
drwxr-xr-x - rawkode 11 Jul 13:34 -- test
drwxr-xr-x - rawkode 11 Jul 13:34 -- tools
drwxr-xr-x - rawkode 14 Jul 23:40 -- top-level
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, there's directories for desktop applications, development stuff, games, and a few other categories.&lt;/p&gt;

&lt;p&gt;Inside of &lt;code&gt;desktops&lt;/code&gt;, we can see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;drwxr-xr-x - rawkode 11 Jul 13:34 -- cdesktopenv
drwxr-xr-x - rawkode 11 Jul 13:34 -- cinnamon
drwxr-xr-x - rawkode 11 Jul 13:34 -- deepin
drwxr-xr-x - rawkode 11 Jul 13:34 -- enlightenment
drwxr-xr-x - rawkode 11 Jul 13:34 -- gnome-2
drwxr-xr-x - rawkode 11 Jul 13:34 -- gnome-3
drwxr-xr-x - rawkode 11 Jul 13:34 -- gnustep
drwxr-xr-x - rawkode 11 Jul 13:34 -- lumina
drwxr-xr-x - rawkode 11 Jul 13:34 -- lxde
drwxr-xr-x - rawkode 11 Jul 13:34 -- lxqt
drwxr-xr-x - rawkode 11 Jul 13:34 -- mate
drwxr-xr-x - rawkode 11 Jul 13:34 -- pantheon
drwxr-xr-x - rawkode 11 Jul 13:34 -- plasma-5
drwxr-xr-x - rawkode 11 Jul 13:34 -- rox
drwxr-xr-x - rawkode 11 Jul 13:34 -- surf-display
drwxr-xr-x - rawkode 11 Jul 13:34 -- xfce
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty much every desktop environment there is ... and if you're shouting in your head&lt;br&gt;
"WHAT ABOUT MY SHITTY ESOTERIC TILING WINDOW MANAGER?" ... then I've got you; it's under&lt;br&gt;
&lt;code&gt;./applications/window-managers&lt;/code&gt;. I love i3, but it's also quite nice being able to change&lt;br&gt;
the volume or pair bluetooth headphones without having to put my beer down and grep some shell&lt;br&gt;
history.&lt;/p&gt;

&lt;p&gt;Notice how I delicately said "shell history" and not &lt;code&gt;zsh&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;fish&lt;/code&gt;, or &lt;code&gt;nu&lt;/code&gt; ... I can't be&lt;br&gt;
bothered getting into another shell debate; 2020's been shit enough.&lt;/p&gt;

&lt;p&gt;Sorry, I've digressed.&lt;/p&gt;

&lt;p&gt;Lets move forward. I've found some similiar packages. I've redacted some of the extensions below&lt;br&gt;
... mostly at random; but you can see that we're in the &lt;code&gt;./pkgs/desktops/gnome-3/extensions&lt;/code&gt; directory&lt;br&gt;
and we have a fair number of example packages to use as a base for our new one.&lt;/p&gt;

&lt;p&gt;Perfect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;pwd

/home/rawkode/Code/src/github.com/NixOS/nixpkgs/pkgs/desktops/gnome-3/extensions
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;ll

drwxr-xr-x - rawkode 11 Jul 13:34 -- appindicator
drwxr-xr-x - rawkode 11 Jul 13:34 -- arc-menu
drwxr-xr-x - rawkode 11 Jul 13:34 -- caffeine
drwxr-xr-x - rawkode 11 Jul 13:34 -- dash-to-dock
drwxr-xr-x - rawkode 11 Jul 21:39 -- dash-to-panel
drwxr-xr-x - rawkode 11 Jul 13:34 -- paperwm
drwxr-xr-x - rawkode 11 Jul 13:34 -- sound-output-device-chooser
drwxr-xr-x - rawkode 11 Jul 13:34 -- topicons-plus
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3. Creating Our Package
&lt;/h3&gt;

&lt;p&gt;I need to create a new directory with a &lt;code&gt;default.nix&lt;/code&gt; and copy over my&lt;br&gt;
example Nix from the sample extension. I started with &lt;code&gt;dash-to-panel&lt;/code&gt;,&lt;br&gt;
as I like that extension. The code for that looks like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;glib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gettext&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gnome-shell-dash-to-panel"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"31"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"home-sweet-gnome"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dash-to-panel"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;rev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0vh36mdncjvfp1jbinifznj5dw3ahsswwm3m9sjw5gydsbx6vh83"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nv"&gt;glib&lt;/span&gt; &lt;span class="nv"&gt;gettext&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nv"&gt;makeFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"INSTALLBASE=$(out)/share/gnome-shell/extensions"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nv"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dash-to-panel@jderose9.github.com"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"An icon taskbar for Gnome Shell"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;licenses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;gpl2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;maintainers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;maintainers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;mounium&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;homepage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/jderose9/dash-to-panel"&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;Unfortunately, there was a problem. This extension uses a &lt;code&gt;Makefile&lt;/code&gt; to document its&lt;br&gt;
build steps (which I recommend for ALL repositories), but unfortunately; emoji-selector&lt;br&gt;
doesn't ship with a &lt;code&gt;Makefile&lt;/code&gt; 😢&lt;/p&gt;

&lt;p&gt;So I'm going to copy the "core" sections and make up the rest from another example shortly.&lt;/p&gt;

&lt;p&gt;What I copied looked like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;glib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gettext&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gnome-shell-emoji-selector"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"19"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"maoschanz"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"emoji-selector-for-gnome"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;rev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0x60pg5nl5d73av494dg29hyfml7fbf2d03wm053vx1q8a3pxbyb"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;glib&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nv"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;"This GNOME shell extension provides a searchable popup menu displaying most emojis"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;licenses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;gpl3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;maintainers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;maintainers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;rawkode&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;homepage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/maoschanz/emoji-selector-for-gnome"&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;Lets break this down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;glib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gettext&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This first line of Nix is in almost every Nix file you'll work with. It's the&lt;br&gt;
imports / dependencies that our Nix script needs from the environment / runtime.&lt;/p&gt;

&lt;p&gt;I like to think of it as similiar to JavaScript's destructing syntax; selecting&lt;br&gt;
only the values we need from the global list. If that's a terrible way to think&lt;br&gt;
about it, I'm sure someone from HackerNews will be along shortly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next up, we need to create a derivation. That's fancy functional talk for a set of values&lt;br&gt;
that allow for some output to be derived.&lt;/p&gt;

&lt;p&gt;The set that we need to build our GNOME Extension contain some obvious facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Repository&lt;/li&gt;
&lt;li&gt;Version / Branch&lt;/li&gt;
&lt;li&gt;Dependencies (Inputs)&lt;/li&gt;
&lt;li&gt;Meta Description
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;  &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gnome-shell-emoji-selector"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"19"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We define the package name and the version. The version is a tag / branch name that you can&lt;br&gt;
get from the Git repository or the GitHub UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;  &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"maoschanz"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"emoji-selector-for-gnome"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;rev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0x60pg5nl5d73av494dg29hyfml7fbf2d03wm053vx1q8a3pxbyb"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fetchFromGitHub&lt;/code&gt; is a function that grabs some source code from GitHub. The&lt;br&gt;
owner, repo, and rev are hopefully self-explanitory. However, we also have &lt;code&gt;sha256&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nix requires builds to be idempotent. That means it uses the sha to verify that&lt;br&gt;
the downloaded and extracted code is what we expected. If it's different, it'll let&lt;br&gt;
us know and we can decide what we want to do.&lt;/p&gt;
&lt;h4&gt;
  
  
  Warning
&lt;/h4&gt;

&lt;p&gt;Word of warning ... if the sha that you use already exists in the Nix store (meaning you copied&lt;br&gt;
it from another example that you had installed), then Nix bypasses the download step and reuses&lt;br&gt;
the local files. This means that I ... you, will install some random extension instead of getting&lt;br&gt;
the actual download you expected.&lt;/p&gt;
&lt;h4&gt;
  
  
  How Do We Get the Sha?
&lt;/h4&gt;

&lt;p&gt;The simplest way is to use lots of 0's. When you try to build&lt;br&gt;
this derivation, Nix will complain that your sha256 doesn't match the repositories download. You can&lt;br&gt;
then copy the sha and update in your derivation.&lt;/p&gt;

&lt;p&gt;Another approach is to calculate the actual sha yourself, using &lt;code&gt;nix-prefetch-url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Go to GitHub and grab the URL for the &lt;code&gt;tar.gz&lt;/code&gt; artefact on the releases page for the revision&lt;br&gt;
or version that you want to add to the repository. Now you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;nix-prefetch-url --unpack https://github.com/maoschanz/emoji-selector-for-gnome/archive/19.tar.gz

unpacking...
[1.0 MiB DL]
path is '/nix/store/694kcbsz38rni0lykffv89ndivgcccks-19.tar.gz'
0x60pg5nl5d73av494dg29hyfml7fbf2d03wm053vx1q8a3pxbyb
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Meta
&lt;/h4&gt;

&lt;p&gt;This is mostly self-explanitory. Just remember to check the license of the package you're adding and&lt;br&gt;
update &lt;code&gt;license =&lt;/code&gt;. I use the &lt;a href="https://github.com/NixOS/nixpkgs/blob/master/lib/licenses.nix"&gt;source code&lt;/a&gt; to&lt;br&gt;
check the correct way to reference the licenses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;  &lt;span class="nv"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;"This GNOME shell extension provides a searchable popup menu displaying most emojis"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;licenses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;gpl3Plus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;maintainers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;maintainers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;rawkode&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;homepage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/maoschanz/emoji-selector-for-gnome"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Dependencies
&lt;/h4&gt;

&lt;p&gt;Understanding the dependencies you need to build a new Nix package can be a little intimidating.&lt;/p&gt;

&lt;p&gt;Fortunately for GNOME Extensions, these don't vary too much and our example actually had this listed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;glib&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to work this out for another package and you don't know where to start? Follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clone Code&lt;/li&gt;
&lt;li&gt;Enter directory&lt;/li&gt;
&lt;li&gt;Create a Nix Shell (nix-shell -p depenendency1 depenendency2 ...)&lt;/li&gt;
&lt;li&gt;Run Build Command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repeat this, adding whatever you need to &lt;code&gt;nix-shell -p&lt;/code&gt; until the build step works.&lt;/p&gt;

&lt;p&gt;If you don't know what your dependency you need is called, search on the &lt;a href="https://nixos.org/nixos/packages.html?channel=nixpkgs-unstable"&gt;Nix Package&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;Example, if I need to build some code that needs Rust, Make, and bash - I'd run: &lt;code&gt;nix-shell -p bash cargo gnumake rustc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To use our repository as an example, if I ran &lt;code&gt;nix-shell&lt;/code&gt; with no dependencies and then ran &lt;code&gt;./install.sh&lt;/code&gt; - then&lt;br&gt;
our install would have failed with &lt;code&gt;glib-compile-schemas&lt;/code&gt; command not found; which is provided by &lt;code&gt;glib&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Skipped Makefile
&lt;/h4&gt;

&lt;p&gt;So I said that we had a problem, the problem was that our example extension used &lt;code&gt;make&lt;/code&gt; and our&lt;br&gt;
current extension doesn't. We know this because our example Nix contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;makeFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"INSTALLBASE=$(out)/share/gnome-shell/extensions"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and our extension repository doesn't have a &lt;code&gt;Makefile&lt;/code&gt;, it only has &lt;code&gt;./install.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Drats.&lt;/p&gt;

&lt;p&gt;Fortunately, I looked at another example (caffeine) and came across the following code.&lt;br&gt;
It turns out that we can manually configure the build steps ourself. Sweet! 🥞&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;  &lt;span class="nv"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"caffeine@patapon.info"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nv"&gt;glib&lt;/span&gt; &lt;span class="nv"&gt;gettext&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nv"&gt;buildPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;bash&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/bash ./update-locale.sh&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    glib-compile-schemas --strict --targetdir=caffeine@patapon.info/schemas/ caffeine@patapon.info/schemas&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;installPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    mkdir -p $out/share/gnome-shell/extensions&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    cp -r &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; $out/share/gnome-shell/extensions&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Customizing the Build
&lt;/h5&gt;

&lt;p&gt;In the code above, we've removed the &lt;code&gt;makeFlags&lt;/code&gt; configuration that we found in the &lt;code&gt;dash-to-panel&lt;/code&gt; Nix package,&lt;br&gt;
because &lt;code&gt;emoji-selector&lt;/code&gt; doesn't have a &lt;code&gt;Makefile&lt;/code&gt;. We've instead provided &lt;code&gt;buildPhase&lt;/code&gt; and &lt;code&gt;installPhase&lt;/code&gt; configuration. These&lt;br&gt;
two different "examples" also used slightly different inputs: &lt;code&gt;buildInputs&lt;/code&gt; and &lt;code&gt;nativeBuildInputs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Argh. What?&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;buildInputs&lt;/code&gt; vs &lt;code&gt;nativeBuildInputs&lt;/code&gt;. This was tricky to track down, it's not that well documented. However,&lt;br&gt;
I did &lt;a href="https://nixos.org/nixpkgs/manual/#ssec-stdenv-dependencies"&gt;find the following&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;nativeBuildInputs&lt;br&gt;
A list of dependencies whose host platform is the new derivation's build platform, and target platform is the new derivation's&lt;br&gt;
host platform. This means a -1 host offset and 0 target offset from the new derivation's platforms. These are programs and libraries&lt;br&gt;
used at build-time that, if they are a compiler or similar tool, produce code to run at run-time—i.e. tools used to build the new&lt;br&gt;
derivation. If the dependency doesn't care about the target platform (i.e. isn't a compiler or similar tool), put it here, rather&lt;br&gt;
than in depsBuildBuild or depsBuildTarget. This could be called depsBuildHost but nativeBuildInputs is used for historical continuity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a mouthful. As I understand it, we use &lt;code&gt;nativeBuildInputs&lt;/code&gt; when we expect the inputs to be build for the platform on our local machine;&lt;br&gt;
and can use &lt;code&gt;buildInputs&lt;/code&gt; for when we don't have such a constraint. I definitely need to understand this more and I'll write more on this&lt;br&gt;
parameter soon; once I've done some more digging 😃&lt;/p&gt;

&lt;p&gt;Next, those phases!&lt;/p&gt;

&lt;p&gt;From the &lt;a href="https://nixos.org/nixpkgs/manual/#ssec-stdenv-attributes"&gt;documentation&lt;/a&gt;; we can see that there's many phases in a build:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;$prePhases unpackPhase patchPhase $preConfigurePhases configurePhase $preBuildPhases buildPhase checkPhase $preInstallPhases&lt;br&gt;
installPhase fixupPhase installCheckPhase $preDistPhases distPhase $postPhases.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each of these are called in this order, unless specifically overridden by the Nix package by providing &lt;code&gt;phases = []&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We need to override the &lt;code&gt;buildPhase&lt;/code&gt; because &lt;a href="https://nixos.org/nixpkgs/manual/#build-phase"&gt;by default&lt;/a&gt; it runs &lt;code&gt;make&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The default buildPhase simply calls make if a file named Makefile, makefile or GNUmakefile exists in the current directory (or the makefile is explicitly set); otherwise it does nothing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is also true for &lt;code&gt;installPhase&lt;/code&gt;, only it tries to run the &lt;a href="https://nixos.org/nixpkgs/manual/#ssec-install-phase"&gt;install target&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The default installPhase creates the directory \$out and calls make install.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a little less magic when you break it down; right? 🧙&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing Our Package
&lt;/h3&gt;

&lt;p&gt;Using the commands inside of &lt;code&gt;./install.sh&lt;/code&gt;, I was able to piece together the config&lt;br&gt;
we needed for our extension to be successfully build and installed by &lt;code&gt;nix&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The only "gotcha" here is that Nix provides &lt;code&gt;$out&lt;/code&gt; variable that provides the directory&lt;br&gt;
our package should install things into.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;glib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gettext&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;

&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="kr"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gnome-shell-emoji-selector"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"19"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;fetchFromGitHub&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"maoschanz"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"emoji-selector-for-gnome"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;rev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0x60pg5nl5d73av494dg29hyfml7fbf2d03wm053vx1q8a3pxbyb"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"emoji-selector@maestroschan.fr"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;glib&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nv"&gt;buildPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    runHooks preBuild&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    glib-compile-schemas ./&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/schemas&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    runHooks postBuild&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;installPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    runHook preInstall&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    mkdir -p $out/share/gnome-shell/extensions&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    cp -r &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; $out/share/gnome-shell/extensions&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    runHook postInstall&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;  ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;"This GNOME shell extension provides a searchable popup menu displaying most emojis"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;licenses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;gpl3Plus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;maintainers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;maintainers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;rawkode&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;homepage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/maoschanz/emoji-selector-for-gnome"&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;So how do we test this to make sure it works?&lt;/p&gt;

&lt;p&gt;We can install it 😁&lt;/p&gt;

&lt;p&gt;To do that, we need to &lt;code&gt;cd&lt;/code&gt; into the &lt;code&gt;nixpkgs&lt;/code&gt; directory; then&lt;br&gt;
install our package using &lt;code&gt;nix-env&lt;/code&gt; and the package name we declared above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;cd ~/Code/src/github.com/NixOS/nixpkgs
&lt;/span&gt;&lt;span class="gp"&gt;nix-env -f $&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; gnome-shell-emoji-selector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assuming all goes to plan, you'll see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;installing 'gnome-shell-emoji-selector-19'
&lt;/span&gt;&lt;span class="gp"&gt;nix-env -f $&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; gnome-shell-emoji-selector  5.30s user 0.43s system 97% cpu 5.873 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉🎉🎉 We did it! 🎉🎉🎉&lt;/p&gt;

&lt;p&gt;Our package installed. Well done. Fire open GitHub and submit a PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;There's still a fair amount to cover. When you submit a PR, you'll be presented with this checklist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- [ ] Tested using sandboxing ([nix.useSandbox](https://nixos.org/nixos/manual/options.html#opt-nix.useSandbox) on NixOS, or option `sandbox` in [`nix.conf`](https://nixos.org/nix/manual/#sec-conf-file) on non-NixOS linux)
- Built on platform(s)
   - [x] NixOS
   - [ ] macOS
   - [ ] other Linux distributions
- [ ] Tested via one or more NixOS test(s) if existing and applicable for the change (look inside [nixos/tests](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests))
- [ ] Tested compilation of all pkgs that depend on this change using `nix-shell -p nixpkgs-review --run "nixpkgs-review wip"`
- [x] Tested execution of all binary files (usually in `./result/bin/`)
- [ ] Determined the impact on package closure size (by running `nix path-info -S` before and after)
- [ ] Ensured that relevant documentation is up to date
- [ ] Fits [CONTRIBUTING.md](https://github.com/NixOS/nixpkgs/blob/master/.github/CONTRIBUTING.md).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've only covered 2 measely steps. In the coming articles, we'll look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sandbox&lt;/li&gt;
&lt;li&gt;macOS Builds&lt;/li&gt;
&lt;li&gt;Nixpkgs on Arch Linux&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until next time&lt;/p&gt;

</description>
      <category>nix</category>
      <category>nixos</category>
      <category>nixpkgs</category>
    </item>
    <item>
      <title>Introduction to WebAssembly on Kubernetes with Krustlet</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Thu, 02 Jul 2020 22:31:16 +0000</pubDate>
      <link>https://dev.to/rawkode/introduction-to-webassembly-on-kubernetes-with-krustlet-33j3</link>
      <guid>https://dev.to/rawkode/introduction-to-webassembly-on-kubernetes-with-krustlet-33j3</guid>
      <description>&lt;p&gt;Welcome! 👋&lt;/p&gt;

&lt;p&gt;This article will aim to introduce you to something wild and wonderful - running WebAssembly binaries on Kubernetes, with Krustlet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;WebAssembly is bytecode that can be executed in your browser, regardless of operating system or architecture; this has opened up a, potentially,&lt;br&gt;
whole new era of web programming; one that doesn't need transpiled to JavaScript.&lt;/p&gt;

&lt;p&gt;Thus far, a few languages have trialed and tested support for compiling their code to WASM for usage within the browser. You can learn about some&lt;br&gt;
of these efforts by checking out these links.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://kripken.github.io/emscripten-site/"&gt;C/C++&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/golang/go/wiki/WebAssembly"&gt;Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oraoto/pib"&gt;PHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rustwasm"&gt;Rust&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swiftwasm.org/"&gt;Swift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://assemblyscript.org/"&gt;TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are more, but I think that's enough to pique your interest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ANY&lt;/strong&gt; language, in the browser ... that's a pretty interesting idea, right? 😉&lt;/p&gt;

&lt;p&gt;One thing we don't often talk about when discussing the browser ... it's pretty darn secure. Running JavaScript in your browser can't&lt;br&gt;
actually do much damage to your host system. This is because the JavaScript engines, such as v8 or SpiderMonkey, run the JavaScript&lt;br&gt;
code in a sandbox; much like modern Linux container systems, like containerd.&lt;/p&gt;

&lt;p&gt;So what if we want to leverage WASM, in our many languages, and benefit from the sandbox out the browser ... but without the browser?&lt;/p&gt;

&lt;p&gt;Enter, WASI and &lt;code&gt;wasmtime&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  WebAssembly System Interface (WASI)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wasi.dev/"&gt;WASI&lt;/a&gt; provides an interface for running WASM code as a process on your operating system, rather than&lt;br&gt;
browsers; which allows us to interact with all the things the browser doesn't: files and filesystems, Berkeley sockets, clocks, and random numbers.&lt;/p&gt;

&lt;p&gt;WASI does this in a fashion that still gives us similiar security boundaries that we get within the browser, but through a capabilities model defined&lt;br&gt;
by the runtime. WASI is not a runtime, but the interface that runtimes must adhere too. This is handled by a capability-based security model.&lt;/p&gt;

&lt;p&gt;How do capabilities work? Lets use an example. Most applications open a file by some reference, which is usually a file path on the filesystem. Any application&lt;br&gt;
that has that reference (file path) can interact with that file, provided the &lt;a href="https://en.wikipedia.org/wiki/Ambient_authority"&gt;Ambient Authority&lt;/a&gt; allows.&lt;/p&gt;

&lt;p&gt;What does that mean? It means that if user &lt;code&gt;rawkode&lt;/code&gt; executes a binary, and &lt;code&gt;rawkode&lt;/code&gt; has permissions to read a file; then that binary can read that file.&lt;/p&gt;

&lt;p&gt;That's not ideal.&lt;/p&gt;

&lt;p&gt;WASI uses a &lt;a href="https://en.wikipedia.org/wiki/Capability-based_security"&gt;Capability model&lt;/a&gt;, rather than ambient authority. When you execute a WASI binary, it can ONLY access the files that you have given that binary&lt;br&gt;
the permission to.&lt;/p&gt;

&lt;p&gt;What does that mean? Think about container images when executed, they can only access the files within the container image, or explictly bind mounted in. WASI is&lt;br&gt;
the same. WASI binaries can only access files if the binary is given permission to access them, usually by providing a directory root.&lt;/p&gt;
&lt;h3&gt;
  
  
  wasmtime
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;wasmtime&lt;/code&gt; is an implemention of WASI. You can think of &lt;code&gt;wasmtime&lt;/code&gt; much like &lt;code&gt;node&lt;/code&gt;. In the same way that &lt;code&gt;node&lt;/code&gt; allowed us to execute JavaScript outside of the browser,&lt;br&gt;
&lt;code&gt;wasmtime&lt;/code&gt; does that for WASM.&lt;/p&gt;
&lt;h3&gt;
  
  
  Krustlet
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/deislabs/krustlet"&gt;Krustlet&lt;/a&gt; is a Kubelet implementation for Kubernetes that supports running WASM bytecode, with the &lt;code&gt;wasmtime&lt;/code&gt; runtime, instead of&lt;br&gt;
container images with a container runtime.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Getting a Kubernetes cluster
&lt;/h3&gt;

&lt;p&gt;For today's tutorial, we're going to use &lt;a href="https://github.com/kubernetes/minikube"&gt;minikube&lt;/a&gt;. &lt;code&gt;minikube&lt;/code&gt;&lt;br&gt;
gives us a full Kubernetes cluster, in a VM, on our local machine. With &lt;code&gt;minikube&lt;/code&gt; installed, create a&lt;br&gt;
cluster with the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;minikube start
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building Krustlet
&lt;/h3&gt;

&lt;p&gt;As this is rather experimental / early stages, you'll need to compile the Krustlet from source. Depending on your&lt;br&gt;
machine, this can take anywhere from 3 minutes to 10 minutes; so you make want to go make a coffee or something ⏰&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Clone the Krustlet &lt;span class="nb"&gt;source &lt;/span&gt;code to a &lt;span class="nb"&gt;local &lt;/span&gt;directory and enter it
&lt;span class="go"&gt;git clone https://github.com/deislabs/krustlet
cd krustlet

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Using Rust&lt;span class="s1"&gt;'s build tool, Cargo, to build the application
&lt;/span&gt;&lt;span class="go"&gt;cargo build
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Bootstrapping
&lt;/h4&gt;

&lt;p&gt;Now that we have the binaries needed to run our Krustlet, we need to perform some initial bootstrapping.&lt;/p&gt;

&lt;p&gt;Bootstrapping is important, because Kubernetes restricts access to the control plane. We need to provide enough&lt;br&gt;
configuration for our Krustlet to register itself with the control plane, so it can become a "Node" within the&lt;br&gt;
cluster.&lt;/p&gt;

&lt;p&gt;You can create this bootstrap configuration with the following command. &lt;strong&gt;PLEASE&lt;/strong&gt; ensure your &lt;code&gt;KUBECONFIG&lt;/code&gt; context is pointing to&lt;br&gt;
your development cluster, and not something in production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;./docs/howto/assets/bootstrap.sh
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see some output, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;secret/bootstrap-token-x4jygy created
Switched to context "minikube".
Context "minikube" renamed to "tls-bootstrap-token-user@kubernetes".
User "tls-bootstrap-token-user" set.
Context "tls-bootstrap-token-user@kubernetes" modified.
Context "tls-bootstrap-token-user@kubernetes" modified.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does this all mean? 🤓&lt;/p&gt;

&lt;p&gt;First, this bootstrap script creates a secret inside of our cluster; using our default &lt;code&gt;KUBECONFIG&lt;/code&gt;.&lt;br&gt;
This will be setup for you when you spin up &lt;code&gt;minikube&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This secret is very similiar to a Kubelets bootstrap secret. It's all very 😴&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;auth-extra-groups:                         #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;system:bootstrappers:kubeadm:default-node-token
&lt;span class="gp"&gt;expiration: MjAyMC0wNy0wMlQxMzozOTo0OVo=   #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;2020-07-02T13:39:49Z &lt;span class="o"&gt;(&lt;/span&gt;1 hour from bootstrap&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;token-id: eDRqeWd5                         #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;x4jygy
&lt;span class="gp"&gt;token-secret: OWR1enFtamdvM3BlaXQ3YQ==     #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;9duzqmjgo3peit7a
&lt;span class="gp"&gt;usage-bootstrap-authentication: dHJ1ZQ==   #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="gp"&gt;usage-bootstrap-signing: dHJ1ZQ==          #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, the bootstrap script grabs your existing &lt;code&gt;KUBECONFIG&lt;/code&gt;, copies it, crates a new user within your cluster,&lt;br&gt;
modifies the context to use that new user, and stores it at &lt;code&gt;~/.krustlet/config/bootstrap.conf&lt;/code&gt;. This &lt;code&gt;bootstrap.conf&lt;/code&gt;&lt;br&gt;
can now be used to provide authentication for our Krustlet to the control plane.&lt;/p&gt;
&lt;h4&gt;
  
  
  Running the Krustlet
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;KUBECONFIG=~/.krustlet/config/bootstrap.conf krustlet-wasi --node-ip $&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;minikube ip&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--port&lt;/span&gt; 3000 &lt;span class="nt"&gt;--bootstrap-file&lt;/span&gt; ~/.krustlet/config/bootstrap.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first time this runs, it will generate the required TLS certificates. You'll need to approve the CSR in another terminal tab / window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;kubectl certificate approve $&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;-tls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets confirm everything looks good, we should see 2 "nodes" now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;NAME       STATUS   ROLES    AGE     VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE               KERNEL-VERSION   CONTAINER-RUNTIME
&lt;/span&gt;&lt;span class="gp"&gt;arch       Ready    agent    3m15s   0.3.0     192.168.39.37   &amp;lt;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&amp;lt;unknown&amp;gt;              &amp;lt;unknown&amp;gt;        mvp
&lt;span class="gp"&gt;minikube   Ready    master   57m     v1.18.3   192.168.39.37   &amp;lt;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;Buildroot 2019.02.10   4.19.107         docker://19.3.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations, you've got a Krustlet running on &lt;code&gt;minikube&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a WASI Application
&lt;/h2&gt;

&lt;p&gt;Next, we're going to throw together a quick "Hello World" style application. Unfortunately, WASI hasn't defined what networking / Berkeley sockets will&lt;br&gt;
look like yet within WASI-land; so we can't run a web server or anything more interesting (yet).&lt;/p&gt;

&lt;p&gt;All in good time though 😁&lt;/p&gt;
&lt;h3&gt;
  
  
  TL;DR
&lt;/h3&gt;

&lt;p&gt;If you don't want to follow the DIY steps below, you can clone my repository and jump straight to the Building section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;git clone https://gitlab.com/rawkode/wasi-hello-world
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  DIY
&lt;/h3&gt;

&lt;p&gt;First, create a Rust project. This command generates a skeleton Rust project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;cargo new --bin wasi-hello-world
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll want to update &lt;code&gt;main.rs&lt;/code&gt; with the following code. This code prints a message,&lt;br&gt;
and sleeps for 5 seconds; it then loops this process indefinitely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c"&gt;// Code from&lt;/span&gt;
&lt;span class="c"&gt;// https://github.com/deislabs/krustlet/blob/master/docs/intro/tutorial01.md&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, from WASI and Krustlet!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&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;h3&gt;
  
  
  Building
&lt;/h3&gt;

&lt;p&gt;We need to enable the WASI target, which we'll do with &lt;code&gt;rustup&lt;/code&gt;. We also need to build our application, targetting WASI as a runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Enable  wasm32-wasi as a compilation targeht
&lt;span class="go"&gt;rustup target add wasm32-wasi

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Build WASI bytecode
&lt;span class="go"&gt;cargo build --target wasm32-wasi
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running &amp;amp; Publishing
&lt;/h3&gt;

&lt;p&gt;When we build a WASI compatible binary, what we get is a &lt;code&gt;wasm&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;❯ ls target/wasm32-wasi/debug
... wasi-hello-world.wasm ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to run WASI bytecode, we use a WASI runtime; like &lt;code&gt;wasmtime&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;❯ wasmtime ./target/wasm32-wasi/debug/wasi-hello-world.wasm
Hello, from WASI and Krustlet!
^C
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oooh, shiny! 🌠&lt;/p&gt;

&lt;p&gt;However, that's not what we want to do. We want to run this on Kubernetes using our Krustlet.&lt;/p&gt;

&lt;p&gt;In-order to do so, we need to "publish" our image to "somewhere".&lt;/p&gt;

&lt;p&gt;Fortunately, there's &lt;code&gt;wasm-to-oci&lt;/code&gt; which allows publishing wasm binaries to OCI compatible registries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Docker Hub and Quay do not support these artifacts at the time of writing, but GitLab, Google Cloud, and Azure Cloud do.&lt;/p&gt;

&lt;p&gt;Sadly, we do need to build this ourselves too; fortunately, it's I've prepared the commands 😃&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;git clone https://github.com/engineerd/wasm-to-oci
cd wasm-to-oci
make
sudo cp ./bin/wasm-to-oci /usr/local/bin/wasm-to-oci
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, lets go back to our &lt;code&gt;wasi-hello-world&lt;/code&gt; directory and push an image with our WASI binary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;You can use this image &lt;span class="k"&gt;if &lt;/span&gt;you want, it&lt;span class="s1"&gt;'s public
&lt;/span&gt;&lt;span class="go"&gt;wasm-to-oci push ./target/wasm32-wasi/debug/wasi-hello-world.wasm registry.gitlab.com:rawkode/wasi-hello-world:latest

INFO[0008] Pushed: registry.gitlab.com/rawkode/wasi-hello-world:latest
INFO[0008] Size: 1808571
INFO[0008] Digest: sha256:a9617d29b859994cc4b6f5ea73f7d68096921f7225ed31e645f0dda7b27163ed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Last Kilometer
&lt;/h2&gt;

&lt;p&gt;OK. We're almost there. Thus far, we've:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☑ Created a Kubernetes cluster with &lt;code&gt;minikube&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;☑ Cloned, compiled, and ran a Krustlet from source&lt;/li&gt;
&lt;li&gt;☑ Created a Rust application and compiled it to WASI&lt;/li&gt;
&lt;li&gt;☑ Converted our WASI binary to an OCI compatible image and pushed it to the GitLab Container Registry&lt;/li&gt;
&lt;li&gt;❌ Run our Rust WASI targetting binary on Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OK, so now we need to schedule our WASI workload on the Krustlet. Like ALL Kubernetes resources, we need YAML for this.&lt;/p&gt;

&lt;p&gt;Save the following to &lt;code&gt;pod.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;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;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wasi-hello-world&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wasi-hello-world&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.gitlab.com/rawkode/wasi-hello-world:latest&lt;/span&gt;
      &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
  &lt;span class="na"&gt;tolerations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;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;krustlet/arch"&lt;/span&gt;
      &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Equal"&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wasm32-wasi"&lt;/span&gt;
      &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NoExecute"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply this YAML file to your Kubernetes cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;kubectl apply -f pod.yaml
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💥 DONE 💥&lt;/p&gt;

&lt;p&gt;We've now deployed a WASI binary, via Krustlet, to our Kubernetes cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Confirm the Awesome
&lt;/h3&gt;

&lt;p&gt;To confirm your WASI binary is running, you can use the Kubernetes tooling to check a few things.&lt;/p&gt;

&lt;p&gt;First, is the pod scheduled and running and do we have log output? Run the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;kubectl get pods
kubectl logs -f wasi-hello-world
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! I hope this helps you on your path to understanding, getting excited, and adopting WASM, WASI, and Krustlet.&lt;/p&gt;

&lt;p&gt;If you have any questions, grab me on &lt;a href="https://twitter.com/rawkode"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until next time 🤘&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>webassembly</category>
    </item>
    <item>
      <title>Tip 6: Bat, Rip, &amp; Skim</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Wed, 08 Jan 2020 23:20:10 +0000</pubDate>
      <link>https://dev.to/rawkode/tip-6-bat-rip-skim-51fd</link>
      <guid>https://dev.to/rawkode/tip-6-bat-rip-skim-51fd</guid>
      <description>&lt;p&gt;Consider this the "Embrace Rust" tip post. If an app can be rewritten in Rust, then someone's probably done it already.&lt;/p&gt;

&lt;p&gt;Here are my top 3 (Besides &lt;code&gt;exa&lt;/code&gt;, as I covered that &lt;a href="https://dev.to/rawkode/tip-5-replacing-ls-with-exa-3o5n"&gt;yesterday&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Bat
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sharkdp/bat"&gt;Bat&lt;/a&gt; is a &lt;code&gt;cat&lt;/code&gt; rewrite, in Rust. It has colours. Lots of colours.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LWJ7TwUE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/dumf43j50jpdah6agzk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LWJ7TwUE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/dumf43j50jpdah6agzk6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ripgrep
&lt;/h2&gt;

&lt;p&gt;Ripgrep, as one can imagine, is a &lt;code&gt;grep&lt;/code&gt; rewrite in Rust. Now, it's not got as many colours (Well, why would it?), but it's &lt;strong&gt;crazy&lt;/strong&gt; fast.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qD0eWXHz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2eqy6zv1awbt6jjnc9ym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qD0eWXHz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2eqy6zv1awbt6jjnc9ym.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Skim
&lt;/h2&gt;

&lt;p&gt;Finally, one without a rusted name. Skim is a &lt;code&gt;fzf&lt;/code&gt; alternative. It's good for presenting lists of content in a filterable fashion. Lets take a look at &lt;code&gt;kill -9&lt;/code&gt; followed by Tab&lt;/p&gt;


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





&lt;p&gt;Thanks for tuning in,&lt;br&gt;
Until next time 🤘&lt;/p&gt;

</description>
      <category>linux</category>
      <category>dotfiles</category>
    </item>
    <item>
      <title>Tip 5: Replacing ls with exa</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Tue, 07 Jan 2020 13:25:53 +0000</pubDate>
      <link>https://dev.to/rawkode/tip-5-replacing-ls-with-exa-3o5n</link>
      <guid>https://dev.to/rawkode/tip-5-replacing-ls-with-exa-3o5n</guid>
      <description>&lt;p&gt;Rust ... slowly but surely, all applications are being rewritten in Rust.&lt;/p&gt;

&lt;p&gt;My next few tips will cover a few of these :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Exa
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ogham/exa"&gt;Exa&lt;/a&gt; is an &lt;code&gt;ls&lt;/code&gt; replacement written in Rust. As they say in their &lt;code&gt;README&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;with more features and better defaults&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What does it look like?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wpW_RGh1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wpyyb12l52k2781dfb9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wpW_RGh1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wpyyb12l52k2781dfb9k.png" alt="Output from Exa"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xxFuMm4j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wnscoi85yr3f8zbbduby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xxFuMm4j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wnscoi85yr3f8zbbduby.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty shiny, eh?&lt;/p&gt;

&lt;p&gt;Things worth noting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Colours that distinguish directories, files, and executable files&lt;/li&gt;
&lt;li&gt;Colours for permissions, owners, and size of file&lt;/li&gt;
&lt;li&gt;Git status of each file in long output&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Trees Everywhere
&lt;/h3&gt;

&lt;p&gt;When working with code, it can be pretty nice to get a &lt;code&gt;tree&lt;/code&gt; style look at the repository. Exa surpasses expectations here too.&lt;/p&gt;

&lt;p&gt;First, here are a few aliases I have configured:&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;# ls&lt;/span&gt;
&lt;span class="nv"&gt;TREE_IGNORE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"cache|log|logs|node_modules|vendor"&lt;/span&gt;

&lt;span class="nb"&gt;alias ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;' exa --group-directories-first'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;la&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;' ls -a'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;ll&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;' ls --git -l'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;' ls --tree -D -L 2 -I ${TREE_IGNORE}'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;ltt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;' ls --tree -D -L 3 -I ${TREE_IGNORE}'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;lttt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;' ls --tree -D -L 4 -I ${TREE_IGNORE}'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;ltttt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;' ls --tree -D -L 5 -I ${TREE_IGNORE}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let me explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--tree&lt;/code&gt; - Enables Tree display&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-D&lt;/code&gt; - Show directories only (Useful for large trees)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-L n&lt;/code&gt; - How many levels deep to display&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-I ${TREE_IGNORE}&lt;/code&gt; - This omits/hides common vendor directories, as defined in the variable above the aliases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a short &lt;code&gt;asciinema&lt;/code&gt; of me using the &lt;code&gt;lt&lt;/code&gt;, &lt;code&gt;ltt&lt;/code&gt;, and &lt;code&gt;lttt&lt;/code&gt; aliases:&lt;/p&gt;


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



</description>
      <category>linux</category>
      <category>dotfiles</category>
      <category>rust</category>
    </item>
    <item>
      <title>Tip 4: Git Commit Templates &amp; Conventional Commits</title>
      <dc:creator>David McKay</dc:creator>
      <pubDate>Mon, 06 Jan 2020 16:18:32 +0000</pubDate>
      <link>https://dev.to/rawkode/tip-4-git-commit-templates-conventional-commits-4g27</link>
      <guid>https://dev.to/rawkode/tip-4-git-commit-templates-conventional-commits-4g27</guid>
      <description>&lt;p&gt;Git commit messages are &lt;strong&gt;important&lt;/strong&gt;. We all know that, yet ... in the heat of the moment, we're prone to this non-sense:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"stuff"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"it'll work this time!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any of them look familiar? You bet your ass they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a Git Commit Template
&lt;/h2&gt;

&lt;p&gt;This is nice and easy. First, create a text file for your template inside your &lt;code&gt;~/.config/git&lt;/code&gt; directory (&lt;code&gt;~/.git/&lt;/code&gt; on macOS I think).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.config/git/commit.txt
My Commit Template
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update your Git configuration. You can do this one of 2 ways:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; commit.template ~/.config/git/commit.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you can update your configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;commit]
&lt;span class="nv"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.config/git/commit.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conventional Commits
&lt;/h2&gt;

&lt;p&gt;Now that you've got a simple template. What should you put there?&lt;/p&gt;

&lt;p&gt;Let me introduce you to &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/"&gt;Conventional Commits&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find my template &lt;a href="https://gitlab.com/rawkode/dotfiles/raw/master/nix/includes/development/git-commit-template.txt"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following is the summary and some examples from the website linked above.&lt;/p&gt;




&lt;h3&gt;
  
  
  Conventional Commits
&lt;/h3&gt;

&lt;p&gt;The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of. This convention dovetails with SemVer, by describing the features, fixes, and breaking changes made in commit messages.&lt;/p&gt;

&lt;p&gt;The commit message should be structured as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;type&amp;gt;[optional scope]: &amp;lt;description&amp;gt;

[optional body]

[optional footer(s)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Examples
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Commit message with description and breaking change footer
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feat: allow provided config object to extend other configs

BREAKING CHANGE: `extends` key in config file is now used for extending other config files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Commit message with &lt;code&gt;!&lt;/code&gt; to draw attention to breaking change
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;refactor!: drop support for Node 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Commit message with both &lt;code&gt;!&lt;/code&gt; and BREAKING CHANGE footer
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;refactor!: drop support for Node 6

BREAKING CHANGE: refactor to use JavaScript features not available in Node 6.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Commit message with no body
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docs: correct spelling of CHANGELOG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Commit message with scope
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feat(lang): add polish language
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Commit message with multi-paragraph body and multiple footers
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fix: correct minor typos in code

see the issue for details

on typos fixed.

Reviewed-by: Z
Refs #133
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>git</category>
      <category>dotfiles</category>
    </item>
  </channel>
</rss>
