<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>sandipb.net</title>
    <link>https://blog.sandipb.net/</link>
    <description>Recent content on sandipb.net</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</managingEditor>
    <webMaster>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</webMaster>
    <copyright>Content licensed under &lt;a rel=&#34;license&#34; href=&#34;http://creativecommons.org/licenses/by/4.0/&#34;&gt;CC-By 4.0&lt;/a&gt;.
</copyright>
    <lastBuildDate>Mon, 06 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.sandipb.net/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Social media crashed a bank in a day, agents will do it before we wake up</title>
      <link>https://blog.sandipb.net/2026/04/06/social-media-crashed-a-bank-in-a-day-agents-will-do-it-before-we-wake-up/</link>
      <pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2026/04/06/social-media-crashed-a-bank-in-a-day-agents-will-do-it-before-we-wake-up/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/2026/stock-market-crash-phone.jpg"
    alt="A smartphone showing a stock market crash with a red downward chart on a dark background">
</figure>

<p>In 2023, the Silicon Valley Bank (SVB) had a historic bank run, with <a href="https://web.archive.org/web/20230312021922/https://fortune.com/2023/03/11/silicon-valley-bank-run-42-billion-attempted-withdrawals-in-one-day/">investors withdrawing $42 billion in a single day</a>. The Federal
Reserve&rsquo;s <a href="https://www.federalreserve.gov/publications/files/svb-review-20230428.pdf">postmortem report</a> essentially blamed two modern technologies — social media and online banking for the incident.</p>



  <blockquote>
    <p>the combination of social media, a highly networked and concentrated depositor base, and technology may have fundamentally changed the
speed of bank runs.</p>

  </blockquote>

<p>It was considered instantaneous in nature, well, compared to how such large-scale withdrawals happened in the past. And the correlation
between social media and the bank run was so strong that an academic paper confirmed it with data:</p>



  <blockquote>
    <p>During the SVB run period, banks with high pre-existing exposure to Twitter lost 4.3 percentage points more stock market value.
ref: <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4422754">Social Media as a Bank Run Catalyst</a></p>

  </blockquote>

<p>But with the pervasive access to agentic AI, especially in the financial sector, which has been innovating with automated trading for years,
people are rightly pointing out that the next financial disaster could happen in a fraction of the time it took for SVB to collapse.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/2026/stock-market-crash-phone.jpg"
    alt="A smartphone showing a stock market crash with a red downward chart on a dark background">
</figure>

<p>In 2023, the Silicon Valley Bank (SVB) had a historic bank run, with <a href="https://web.archive.org/web/20230312021922/https://fortune.com/2023/03/11/silicon-valley-bank-run-42-billion-attempted-withdrawals-in-one-day/">investors withdrawing $42 billion in a single day</a>. The Federal
Reserve&rsquo;s <a href="https://www.federalreserve.gov/publications/files/svb-review-20230428.pdf">postmortem report</a> essentially blamed two modern technologies — social media and online banking for the incident.</p>



  <blockquote>
    <p>the combination of social media, a highly networked and concentrated depositor base, and technology may have fundamentally changed the
speed of bank runs.</p>

  </blockquote>

<p>It was considered instantaneous in nature, well, compared to how such large-scale withdrawals happened in the past. And the correlation
between social media and the bank run was so strong that an academic paper confirmed it with data:</p>



  <blockquote>
    <p>During the SVB run period, banks with high pre-existing exposure to Twitter lost 4.3 percentage points more stock market value.
ref: <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4422754">Social Media as a Bank Run Catalyst</a></p>

  </blockquote>

<p>But with the pervasive access to agentic AI, especially in the financial sector, which has been innovating with automated trading for years,
people are rightly pointing out that the next financial disaster could happen in a fraction of the time it took for SVB to collapse.</p>
<h2 id="now-imagine-agents-doing-this">Now imagine agents doing this</h2>
<p>Here is an excerpt from the <a href="https://www.washingtonpost.com/newsletters/ai-tech-brief/">Washington Post&rsquo;s AI &amp; Tech brief</a> today that captures this concern:</p>



  <blockquote>
    <p>Agentic AI in finance &ldquo;means you&rsquo;re going to have bank runs in the middle of the night based on rumors on Reddit or Moltbook,&rdquo; said
Foster, referring to the social network built for AI agents.</p>

  </blockquote>

<p>For trading, now even the friction of humans socializing online and reacting to rumors (credibly or not) is removed.</p>
<p><a href="https://web.archive.org/web/20260311045354/https://www.dw.com/en/moltbook-explained-viral-site-for-ai-bots-goes-mainstream/a-75779970">Even AI agents can socialize now</a>, which when I first heard about it, I thought was someone&rsquo;s silly idea. But I kind of see how that can be
useful somewhat, for personalized agentic workflows reacting to external signals. Who would not want to automatically buy tickets for a
concert the moment the rumors of it being available start surfacing online?</p>
<p>But there can be serious consequences of such a system in the
financial markets, especially when the agents are programmed by less experienced end users. Actually, it is less to do with the experience
of the end users, but rather the scale such access opens up. Even if a small percentage of users have their agents set up to react to market
signals, the sheer number of users and agents could lead to a cascade effect.</p>
<p>We recently had meme stocks like AMC and GameStop get hammered by users on online forums. I can&rsquo;t imagine what would happen to companies if
agents get into that game. I can imagine the authorities banning automated trading, but agentic AI can so very easily assume the identity of
their users, launching regular browsers, navigating like humans, making it really difficult to police.</p>
<h2 id="crypto-has-no-closing-bell">Crypto has no closing bell</h2>
<p>And for assets that are traded 24/7, like cryptocurrencies, I wonder if there is even a way to put a circuit breaker in place. I mean crypto
already has crazy volatility. With mass agentic AI trading, I can imagine it being a disaster.</p>
<p>We have already seen some of this happening recently.</p>
<p>On 11th October 2025, when a US tariff hike on China was announced, $19 billion worth of crypto was liquidated.</p>



  <blockquote>
    <p>In the stock market, this would have triggered at least two trading halts and a restart auction. In the crypto world, it simply triggered
panic among investors, who knew there was no &ldquo;limit down&rdquo; option to save them. They could only watch helplessly as crypto&rsquo;s worst
liquidation event unfolded while the code continued to perform its programmed task.
ref: <a href="https://web.archive.org/web/20251212092543/https://www.panewslab.com/en/articles/91930346-4371-44bb-8f09-b5a8123f7650">The Price of Freedom: Does Crypto Need Its Own Circuit Breakers?</a></p>

  </blockquote>

<p>And in February 2026, a vulnerability in AI trading agents on Solana DeFi led to a $45 million breach, with no opening bell to pause the damage.
ref: <a href="https://web.archive.org/web/20260407050737/https://www.kucoin.com/blog/en-ai-trading-agent-vulnerability-2026-how-a-45m-crypto-security-breach-exposed-protocol-risks">AI Trading Agent $45M Crypto Breach</a></p>
<h2 id="and-theyre-easy-to-fool">And they&rsquo;re easy to fool</h2>
<p>What makes it even worse is that these AI agents can very easily be fooled by misinformation inserted by prompt
injections. A <a href="https://www.cointribune.com/en/a-deepmind-study-highlights-six-major-vulnerabilities-of-ai-agents/">recent study from Google DeepMind</a> identified six categories of &ldquo;traps&rdquo; that can exploit AI agents, and found that
content injection attacks trapped AI agents 86% of the time in tested scenarios.</p>



  <blockquote>
    <p>A fake financial report released at the right time could trigger synchronized sell orders among thousands of AI trading agents.</p>

  </blockquote>

<hr>
<p><small>Cover photo by <a href="https://unsplash.com/@jamie452">Jamie Street</a> on <a href="https://unsplash.com/photos/VP4WmibxvcY">Unsplash</a></small></p> ]]></content:encoded>
    </item>
    
    <item>
      <title>TIL Hugo can figure out image dimensions at build time</title>
      <link>https://blog.sandipb.net/2026/04/04/til-hugo-can-figure-out-image-dimensions-at-build-time/</link>
      <pubDate>Sat, 04 Apr 2026 00:00:00 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2026/04/04/til-hugo-can-figure-out-image-dimensions-at-build-time/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/hugo.png">
</figure>

<p>I&rsquo;d been hardcoding width and height attributes in my Hugo templates to prevent layout shift. It worked fine, but it was
tedious — every time I changed an image, I had to look up the new dimensions and update the template by hand.</p>
<p>Today, I had to add a new image to the sidebar, and I felt lazy enough to ask copilot to find the dimensions for me and
insert them into the template. It instead did something unexpected. It used an odd new Hugo function called
<a href="https://web.archive.org/web/20180123120411/http://gohugo.io/functions/imageconfig"><code>imageConfig</code></a> instead.</p>
<p>Curious, I looked it up. I haven&rsquo;t kept myself up to date with Hugo&rsquo;s latest features the last few years. The
embarrassing part is that, as it turns out, this isn&rsquo;t a new function. It was added in 2017 (!), but I hadn&rsquo;t heard of
it until now. I had no idea that Hugo could discover image dimensions at build time. Seems I really should read up more
about what all Hugo can do.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/hugo.png">
</figure>

<p>I&rsquo;d been hardcoding width and height attributes in my Hugo templates to prevent layout shift. It worked fine, but it was
tedious — every time I changed an image, I had to look up the new dimensions and update the template by hand.</p>
<p>Today, I had to add a new image to the sidebar, and I felt lazy enough to ask copilot to find the dimensions for me and
insert them into the template. It instead did something unexpected. It used an odd new Hugo function called
<a href="https://web.archive.org/web/20180123120411/http://gohugo.io/functions/imageconfig"><code>imageConfig</code></a> instead.</p>
<p>Curious, I looked it up. I haven&rsquo;t kept myself up to date with Hugo&rsquo;s latest features the last few years. The
embarrassing part is that, as it turns out, this isn&rsquo;t a new function. It was added in 2017 (!), but I hadn&rsquo;t heard of
it until now. I had no idea that Hugo could discover image dimensions at build time. Seems I really should read up more
about what all Hugo can do.</p>
<p>Anyway, I found out that it is part of what Hugo calls &ldquo;<a href="https://gohugo.io/hugo-pipes/introduction/">Hugo Pipes</a>&rdquo; — an
asset processing pipeline that allows you to do a lot of things with your assets at build time.</p>
<p>It needs me to move my images from the <code>static/</code> directory to the <code>assets/</code> directory. And then I can use
<a href="https://gohugo.io/content-management/image-processing/#page"><code>resources.Get</code></a> (even <code>imageConfig</code> is considered
to be legacy) at build time, discover the dimensions of the image, and get the URL via <code>.RelPermalink</code>.</p>
<p>So earlier, I had to do this in my template:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">img</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/images/kagi-small-web.png&#34;</span> <span style="color:#a6e22e">width</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;202&#34;</span> <span style="color:#a6e22e">height</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;64&#34;</span> 
</span></span><span style="display:flex;"><span>     <span style="color:#a6e22e">alt</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Small Web seal&#34;</span>&gt;
</span></span></code></pre></div><p>Now, I can do this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>{{ with resources.Get &#34;images/kagi-small-web.png&#34; }}
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">img</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ .RelPermalink }}&#34;</span> <span style="color:#a6e22e">width</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ .Width }}&#34;</span> <span style="color:#a6e22e">height</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;{{ .Height }}&#34;</span> 
</span></span><span style="display:flex;"><span>       <span style="color:#a6e22e">alt</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Small Web seal&#34;</span>&gt;
</span></span><span style="display:flex;"><span>{{ end }}
</span></span></code></pre></div> ]]></content:encoded>
    </item>
    
    <item>
      <title>Terraform MCP server makes a huge difference to TF code accuracy</title>
      <link>https://blog.sandipb.net/2026/01/06/terraform-mcp-server-makes-a-huge-difference-to-tf-code-accuracy/</link>
      <pubDate>Tue, 06 Jan 2026 01:29:08 -0500</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2026/01/06/terraform-mcp-server-makes-a-huge-difference-to-tf-code-accuracy/</guid>
      <description><![CDATA[<p>I have wasted too many tokens getting AI editors to work well with Terraform code. The authoritative JavaScript-heavy
provider documentation website makes it impossible to provide as a suitable reference to AI editors. Even adding those
links to Cursor doc index doesn&rsquo;t work. So you get hallucinations and completely wrong code from even the best of the
models.</p>]]></description>
      <content:encoded><![CDATA[      <p>I have wasted too many tokens getting AI editors to work well with Terraform code. The authoritative JavaScript-heavy
provider documentation website makes it impossible to provide as a suitable reference to AI editors. Even adding those
links to Cursor doc index doesn&rsquo;t work. So you get hallucinations and completely wrong code from even the best of the
models.</p>
<figure class="full"><img src="/images/2026/cursor-tf-mcp.png"
    alt="Terraform MCP Server in Cursor"><figcaption>
      <p>Terraform MCP server config in cursor</p>
    </figcaption>
</figure>

<p>Today I discovered that <a href="https://github.com/hashicorp/terraform-mcp-server">there is a MCP server for Terraform</a> which
can provide all the provider metadata to AI editors, and the resultant code is just completely correct in one-shot. The
best part is that AI editors can easily find the versions of providers you are really using, and can request
documentation for those specific versions.</p>
<p><a href="https://notes.sandipb.net/Tech/Cursor+Terraform+MCP+server">I made a note about how to add the MCP server to Cursor</a>.
Just a heads-up, you need to have Docker running on your machine to run the MCP server.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Setting Up a Private Helm Chart Repository with Chartmuseum</title>
      <link>https://blog.sandipb.net/2025/12/18/setting-up-a-private-helm-chart-repository-with-chartmuseum/</link>
      <pubDate>Thu, 18 Dec 2025 03:21:04 -0500</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2025/12/18/setting-up-a-private-helm-chart-repository-with-chartmuseum/</guid>
      <description><![CDATA[<!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/chartmuseum.png"
    alt="Chartmuseum">
</figure>

<p>I have been using Helm charts, like everyone else, for the Kubernetes cluster in my homelab. Until a few months back, I
never gave a thought to the reliability of the Helm chart repositories I was using. And then the <a href="https://web.archive.org/web/20251001080046/https://thenewstack.io/broadcom-ends-free-bitnami-images-forcing-users-to-find-alternatives/">Bitnami news
dropped</a> where they announced that they were going to stop supporting their Helm chart repositories.</p>
<p>Everyone has been scrambling to handle this situation, and most are settling on one of two options:</p>
<ol>
<li>Vendor the sources of existing charts in the Git repositories.</li>
<li>Use a Helm chart repository, paid or free, to mirror them in a more scalable way.</li>
</ol>]]></description>
      <content:encoded><![CDATA[      <!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/chartmuseum.png"
    alt="Chartmuseum">
</figure>

<p>I have been using Helm charts, like everyone else, for the Kubernetes cluster in my homelab. Until a few months back, I
never gave a thought to the reliability of the Helm chart repositories I was using. And then the <a href="https://web.archive.org/web/20251001080046/https://thenewstack.io/broadcom-ends-free-bitnami-images-forcing-users-to-find-alternatives/">Bitnami news
dropped</a> where they announced that they were going to stop supporting their Helm chart repositories.</p>
<p>Everyone has been scrambling to handle this situation, and most are settling on one of two options:</p>
<ol>
<li>Vendor the sources of existing charts in the Git repositories.</li>
<li>Use a Helm chart repository, paid or free, to mirror them in a more scalable way.</li>
</ol>
<p>I began with the first option, but it has left me feeling uneasy. So I looked into the second option, and found two
alternatives: <a href="https://chartmuseum.com/">Chartmuseum</a> and <a href="https://goharbor.io/">Harbor</a>.</p>
<p>Harbor seemed full-featured and OCI-compliant, and it handled both Helm and Docker artifacts. But it seemed a bit overkill for
my needs. So I went with Chartmuseum, which was pretty simple to set up with Docker compose on my Synology NAS and
storage on my local Minio instance.</p>
<p>Another factor was that Chartmuseum supported multi-tenancy, which for some strange reason is missing from OCI
registries like Harbor. I really don&rsquo;t like the idea of having a flat namespace for charts from different organizations.
Multi-tenancy allows me to create a separate mirror for each of the upstream repos, preventing even the slightest possibility of them sharing charts with the same name.</p>
<p>I <a href="https://notes.sandipb.net/Tech/Chartmuseum">documented my whole setup</a> on my notes website, in case it helps someone else.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Ubuntu Wi-Fi 5GHz Issue on Raspberry Pi 5</title>
      <link>https://blog.sandipb.net/2025/12/13/ubuntu-wi-fi-5ghz-issue-on-raspberry-pi-5/</link>
      <pubDate>Sat, 13 Dec 2025 16:18:14 -0500</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2025/12/13/ubuntu-wi-fi-5ghz-issue-on-raspberry-pi-5/</guid>
      <description><![CDATA[<!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/rpi.jpg"
    alt="Raspberry Pi 5">
</figure>

<p>I normally install Ubuntu on my Raspberry Pi machines because I am comfortable with its ecosystem. Most of the time
though, I have been using these boxes connected to my network via Ethernet.</p>
<p>Recently, I got a new Raspberry Pi 5, and as usual, I installed Ubuntu on it. This time I used <a href="https://www.raspberrypi.com/products/m2-hat-plus/">the official HAT</a>
to install the OS on an NVMe drive. The Raspberry Pi imager tool does a great job of setting up the machine with Wi-Fi
enabled. What I never paid attention to was how much the country setting in the options affects the Wi-Fi band.</p>
<p>After booting up the machine, I noticed that the Wi-Fi band was set to 2.4GHz. I found it odd. What followed was a lot of detail that, as usual, I wish I didn&rsquo;t need to know, but now had to. :(</p>]]></description>
      <content:encoded><![CDATA[      <!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/rpi.jpg"
    alt="Raspberry Pi 5">
</figure>

<p>I normally install Ubuntu on my Raspberry Pi machines because I am comfortable with its ecosystem. Most of the time
though, I have been using these boxes connected to my network via Ethernet.</p>
<p>Recently, I got a new Raspberry Pi 5, and as usual, I installed Ubuntu on it. This time I used <a href="https://www.raspberrypi.com/products/m2-hat-plus/">the official HAT</a>
to install the OS on an NVMe drive. The Raspberry Pi imager tool does a great job of setting up the machine with Wi-Fi
enabled. What I never paid attention to was how much the country setting in the options affects the Wi-Fi band.</p>
<p>After booting up the machine, I noticed that the Wi-Fi band was set to 2.4GHz. I found it odd. What followed was a lot of detail that, as usual, I wish I didn&rsquo;t need to know, but now had to. :(</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ iwconfig wlan0
</span></span><span style="display:flex;"><span>wlan0     IEEE 802.11  ESSID:&#34;xxx&#34;
</span></span><span style="display:flex;"><span>          Mode:Managed  Frequency:2.462 GHz  Access Point: 60:22:32:xxx:xxx:xxx
</span></span><span style="display:flex;"><span>          Bit Rate=72.2 Mb/s   Tx-Power=31 dBm
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>Turns out that the country setting affects what is called the <a href="https://docs.kernel.org/networking/regulatory.html">regulatory domain</a> (kernel.org) for the Wi-Fi configuration. This regulates the frequency bands that the wireless card can use.</p>

<div class="aside-note" >
	<div class="aside-text" >You can see the wifi regulatory domain by running the following command: <code>iw reg get</code>.</div>
</div>

<p>Because I didn&rsquo;t pay attention to the country setting, the first time it was mistakenly set to <code>AE</code> (United Arab Emirates). Because of this setting the kernel command line in Raspberry Pi bootloader was set to <code>AE</code>. I also saw that <code>cloud-init</code> configured <code>netplan</code> to set the <code>AE</code> regulatory domain on the wifi interface.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ cat /mnt/cmdline.txt
</span></span><span style="display:flex;"><span>console=serial0,115200 multipath=off dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc cfg80211.ieee80211_regdom=AE
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cat /etc/netplan/50-cloud-init.yaml
</span></span><span style="display:flex;"><span>network:
</span></span><span style="display:flex;"><span>  version: 2
</span></span><span style="display:flex;"><span>  wifis:
</span></span><span style="display:flex;"><span>    wlan0:
</span></span><span style="display:flex;"><span>      optional: true
</span></span><span style="display:flex;"><span>      dhcp4: true
</span></span><span style="display:flex;"><span>      regulatory-domain: &#34;AE&#34;
</span></span></code></pre></div><p>This was preventing the wifi card from using the 5GHz band. I tried to change the country setting by running
<code>iw reg set CA</code> (Canada), and even changed the kernel command line, but it didn&rsquo;t work.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>root@rpi3:~# iw reg get
</span></span><span style="display:flex;"><span>global
</span></span><span style="display:flex;"><span>country CA: DFS-FCC
</span></span><span style="display:flex;"><span>	(2402 - 2472 @ 40), (N/A, 30), (N/A)
</span></span><span style="display:flex;"><span>	(5150 - 5250 @ 80), (N/A, 23), (N/A), NO-OUTDOOR, AUTO-BW
</span></span><span style="display:flex;"><span>	(5250 - 5350 @ 80), (N/A, 24), (0 ms), DFS, AUTO-BW
</span></span><span style="display:flex;"><span>	(5470 - 5600 @ 80), (N/A, 24), (0 ms), DFS
</span></span><span style="display:flex;"><span>	(5650 - 5730 @ 80), (N/A, 24), (0 ms), DFS
</span></span><span style="display:flex;"><span>	(5735 - 5835 @ 80), (N/A, 30), (N/A)
</span></span><span style="display:flex;"><span>	(5925 - 7125 @ 320), (N/A, 12), (N/A), NO-OUTDOOR
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>phy#0
</span></span><span style="display:flex;"><span>country 99: DFS-UNSET
</span></span><span style="display:flex;"><span>	(2402 - 2482 @ 40), (6, 20), (N/A)
</span></span><span style="display:flex;"><span>	(2474 - 2494 @ 20), (6, 20), (N/A)
</span></span><span style="display:flex;"><span>	(5140 - 5360 @ 160), (6, 20), (N/A)
</span></span><span style="display:flex;"><span>	(5460 - 5860 @ 160), (6, 20), (N/A)
</span></span></code></pre></div><p>While the global setting was set to Canada, it is overridden by the country setting for the <code>phy#0</code> interface - the
<code>country 99: DFS-UNSET</code> setting.</p>
<p>According to the <a href="https://wireless.docs.kernel.org/en/latest/en/developers/regulatory/processing_rules.html">Linux kernel documentation on wireless regulatory processing rules</a>:</p>



  <blockquote>
    <p>Upon the initialization of the wireless core (cfg80211) a world regulatory domain (highly restrictive) will be set as
the central regulatory domain. If you get a 00 country code it means you are world roaming.</p>
<p>&hellip;</p>
<p>World roaming means we cannot initiate radiation on certain channels given that certain countries may prohibit
initiating radiation on some channels or may require DFS master support prior to initiating any radiation.</p>

  </blockquote>

<p>This means that unless the phy#0 interface is set to a country other than the world roaming (country &ldquo;00&rdquo; is world
roaming; &ldquo;99&rdquo; means the driver couldn&rsquo;t determine a country code and therefore similar behavior), the wifi card will not
initiate radiation on the 5GHz band.</p>
<p>And why is the <code>phy#0</code> interface set to the world roaming when the global setting is set to Canada? I spent a few hours
scouring the internet for answers, but didn&rsquo;t find anything at first. Then I found <a href="https://www.reddit.com/r/raspberry_pi/comments/1n0vypb/raspberry_5_no_5ghz_band_cant_set_wifi_country/">a post about a person who switched
back from Ubuntu to the Raspberry Pi OS and the wifi started working</a>. Turns out that the Raspberry Pi OS uses an
updated firmware version which honors the global setting.</p>
<p>I could confirm this by checking the firmware version on the Raspberry Pi OS and Ubuntu.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ <span style="color:#75715e"># On Raspberry Pi</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cat /lib/firmware/brcm/brcmfmac43455-sdio.bin | strings| grep -Eo <span style="color:#e6db74">&#34;Version: \S+&#34;</span>
</span></span><span style="display:flex;"><span>Version: 7.45.265
</span></span><span style="display:flex;"><span>$ lsb_release -d
</span></span><span style="display:flex;"><span>Description:	Debian GNU/Linux 13 (trixie)
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ <span style="color:#75715e"># On Ubuntu</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ zstdcat /lib/firmware/brcm/brcmfmac43455-sdio.bin.zst | strings| grep -Eo <span style="color:#e6db74">&#34;Version: \S+&#34;</span>
</span></span><span style="display:flex;"><span>Version: 7.45.234
</span></span><span style="display:flex;"><span>$ lsb_release -d
</span></span><span style="display:flex;"><span>Description:	Ubuntu 24.04.3 LTS
</span></span></code></pre></div><p>For now, I will stick with the Raspberry Pi OS, till the firmware on Ubuntu releases catches up.</p>
<p>What an absolute waste of time otherwise, but learning about the logic around kernel wifi regulatory domain handling was fascinating.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Integrating Homebrew bash completion on Linux with system completions</title>
      <link>https://blog.sandipb.net/2025/12/08/integrating-homebrew-bash-completion-on-linux-with-system-completions/</link>
      <pubDate>Mon, 08 Dec 2025 10:07:33 -0500</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2025/12/08/integrating-homebrew-bash-completion-on-linux-with-system-completions/</guid>
      <description><![CDATA[<p>I like to use <a href="https://brew.sh">Homebrew</a> on my Linux development machine as well, instead of random apt packages which
may or may not be up to date for common tools.</p>
<p>One annoyance I found a solution for, is getting the bash completion for Homebrew commands to work on Linux. The
problem is that Ubuntu (and other Linux distributions) have their own bash completion scripts for system commands. But
the way most bash completion scripts work is that they have a check to see if completions are already loaded.</p>
<p>So if I have a line in my bashrc to load homebrew completions, it won&rsquo;t load, because it detects the completions from
the system already loaded. Specifically, it looks for the environment variable <code>BASH_COMPLETION_VERSINFO</code> which is set
by any bash completion script that is already loaded.</p>]]></description>
      <content:encoded><![CDATA[      <p>I like to use <a href="https://brew.sh">Homebrew</a> on my Linux development machine as well, instead of random apt packages which
may or may not be up to date for common tools.</p>
<p>One annoyance I found a solution for, is getting the bash completion for Homebrew commands to work on Linux. The
problem is that Ubuntu (and other Linux distributions) have their own bash completion scripts for system commands. But
the way most bash completion scripts work is that they have a check to see if completions are already loaded.</p>
<p>So if I have a line in my bashrc to load homebrew completions, it won&rsquo;t load, because it detects the completions from
the system already loaded. Specifically, it looks for the environment variable <code>BASH_COMPLETION_VERSINFO</code> which is set
by any bash completion script that is already loaded.</p>
<h2 id="solution">Solution</h2>
<p>The only satisfactory solution I found is to disable the bash completion for system commands.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mv /etc/profile.d/bash_completion.sh /etc/profile.d/bash_completion.sh.disabled
</span></span></code></pre></div><p>My only concern was that I might lose completions for system commands. However, it seems the Homebrew completion loader also checks for system completion scripts.</p>
<p>You can verify that it works here:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ <span style="color:#75715e"># A homebrew command</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ which helm
</span></span><span style="display:flex;"><span>/home/linuxbrew/.linuxbrew/bin/helm
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ complete -p helm
</span></span><span style="display:flex;"><span>complete -o default -F __start_helm helm
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ <span style="color:#75715e"># A system command</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ which apt
</span></span><span style="display:flex;"><span>/usr/bin/apt
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ complete -p apt
</span></span><span style="display:flex;"><span>complete -F _apt apt
</span></span></code></pre></div><!-- markdownlint-disable-next-line -->

<div class="aside-note" >
	<div class="aside-text" >Completions are lazy loaded. <code>complete -p</code> for a command will only show the completion function if you have exercised the completion for the command once using <code>COMMAND&lt;TAB&gt;&lt;TAB&gt;</code>.</div>
</div> ]]></content:encoded>
    </item>
    
    <item>
      <title>System Design - Tap Compare</title>
      <link>https://blog.sandipb.net/2025/12/08/system-design-tap-compare/</link>
      <pubDate>Mon, 08 Dec 2025 08:36:30 -0500</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2025/12/08/system-design-tap-compare/</guid>
      <description><![CDATA[<p>From the <a href="https://blog.bytebytego.com/p/how-reddit-migrated-comments-functionality">ByteByteGo post</a> on Reddit Comments
migration:</p>
<blockquote>
Reddit used a testing technique called “tap compare” for read migrations. The concept is straightforward:
<ul>
<li>A small percentage of traffic gets routed to the new Go microservice.</li>
<li>The new service generates its response internally.</li>
<li>Before returning anything, it calls the old Python endpoint to get that response too.</li>
<li>The system compares both responses and logs any differences.</li>
<li>The old endpoint’s response is what actually gets returned to users.</li>
</ul>
<p>This approach meant that if the new service had bugs, users never saw them. The team got to validate their new code in
production with real traffic while maintaining zero risk to user experience.</p>
</blockquote>]]></description>
      <content:encoded><![CDATA[      <p>From the <a href="https://blog.bytebytego.com/p/how-reddit-migrated-comments-functionality">ByteByteGo post</a> on Reddit Comments
migration:</p>
<blockquote>
Reddit used a testing technique called “tap compare” for read migrations. The concept is straightforward:
<ul>
<li>A small percentage of traffic gets routed to the new Go microservice.</li>
<li>The new service generates its response internally.</li>
<li>Before returning anything, it calls the old Python endpoint to get that response too.</li>
<li>The system compares both responses and logs any differences.</li>
<li>The old endpoint’s response is what actually gets returned to users.</li>
</ul>
<p>This approach meant that if the new service had bugs, users never saw them. The team got to validate their new code in
production with real traffic while maintaining zero risk to user experience.</p>
</blockquote>
<p>Other references that I have found on this topic:</p>
<ul>
<li><a href="https://web.archive.org/web/20250327025853/https://www.infoq.com/articles/tap-compare-diferencia/">Tap Compare Testing with Diferencia and Java Microservices (InfoQ)</a></li>
</ul> ]]></content:encoded>
    </item>
    
    <item>
      <title>Using UV to install Python</title>
      <link>https://blog.sandipb.net/2025/01/09/using-uv-to-install-python/</link>
      <pubDate>Thu, 09 Jan 2025 16:13:39 -0500</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2025/01/09/using-uv-to-install-python/</guid>
      <description><![CDATA[<p>Interesting that I can now use <a href="https://docs.astral.sh/uv/">uv</a> to install Python. They have a
<a href="https://docs.astral.sh/uv/guides/install-python/">guide</a>.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">You can now install the latest Python 3.14 alpha with uv <a href="https://t.co/yw24LKW7nq">pic.twitter.com/yw24LKW7nq</a></p>&mdash; Charlie Marsh (@charliermarsh) <a href="https://twitter.com/charliermarsh/status/1876691032961642935?ref_src=twsrc%5Etfw">January 7, 2025</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>]]></description>
      <content:encoded><![CDATA[      <p>Interesting that I can now use <a href="https://docs.astral.sh/uv/">uv</a> to install Python. They have a
<a href="https://docs.astral.sh/uv/guides/install-python/">guide</a>.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">You can now install the latest Python 3.14 alpha with uv <a href="https://t.co/yw24LKW7nq">pic.twitter.com/yw24LKW7nq</a></p>&mdash; Charlie Marsh (@charliermarsh) <a href="https://twitter.com/charliermarsh/status/1876691032961642935?ref_src=twsrc%5Etfw">January 7, 2025</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<p>I tried it out.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ uv python install 3.14
</span></span><span style="display:flex;"><span>Installed Python 3.14.0a3 in 1.22s
</span></span><span style="display:flex;"><span> + cpython-3.14.0a3-macos-aarch64-none
</span></span></code></pre></div><p>You can only run the installed Python via uv.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ uv run --python 3.14 python
</span></span><span style="display:flex;"><span>Python 3.14.0a3 (main, Jan  5 2025, 06:22:51) [Clang 19.1.6 ] on darwin
</span></span><span style="display:flex;"><span>Type &#34;help&#34;, &#34;copyright&#34;, &#34;credits&#34; or &#34;license&#34; for more information.
</span></span><span style="display:flex;"><span>&gt;&gt;&gt;
</span></span></code></pre></div><p>Interestingly, <code>uv</code> can detect Pythons installed via other mechanisms.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ uv python list --only-installed
</span></span><span style="display:flex;"><span>cpython-3.14.0a3-macos-aarch64-none    .local/share/uv/python/cpython-3.14.0a3-macos-aarch64-none/bin/python3.14
</span></span><span style="display:flex;"><span>cpython-3.12.5-macos-aarch64-none      /opt/homebrew/opt/python@3.12/bin/python3.12 -&gt; ../Frameworks/Python.framework/Versions/3.12/bin/python3.12
</span></span><span style="display:flex;"><span>cpython-3.12.5-macos-aarch64-none      .asdf/installs/python/3.12.5/bin/python3.12
</span></span><span style="display:flex;"><span>cpython-3.12.5-macos-aarch64-none      .asdf/installs/python/3.12.5/bin/python3 -&gt; python3.12
</span></span><span style="display:flex;"><span>cpython-3.12.5-macos-aarch64-none      .asdf/installs/python/3.12.5/bin/python -&gt; python3.12
</span></span><span style="display:flex;"><span>cpython-3.11.9-macos-aarch64-none      /opt/homebrew/opt/python@3.11/bin/python3.11 -&gt; ../Frameworks/Python.framework/Versions/3.11/bin/python3.11
</span></span><span style="display:flex;"><span>cpython-3.11.6-macos-aarch64-none      .asdf/installs/python/3.11.6/bin/python3.11
</span></span><span style="display:flex;"><span>cpython-3.9.6-macos-aarch64-none       /Applications/Xcode.app/Contents/Developer/usr/bin/python3 -&gt; ../../Library/Frameworks/Python3.framework/Versions/3.9/bin/python3
</span></span></code></pre></div><p>And you can run those Python installations via <code>uv</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ uv run --python 3.9.6 python --version
</span></span><span style="display:flex;"><span>Python 3.9.6
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ uv run --python 3.11.9 python --version
</span></span><span style="display:flex;"><span>Python 3.11.9
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ uv run --python 3.12.6 python --version
</span></span><span style="display:flex;"><span>Python 3.12.6
</span></span></code></pre></div><p>However, that last one above was not in the list. <code>uv</code> downloaded it on-demand and ran it. This seems to be the default behavior which can be overridden by an environment variable.</p>
<p>Let me uninstall and try again with a command line option which is equivalent to the environment variable.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ uv python uninstall 3.12.6
</span></span><span style="display:flex;"><span>Searching for Python versions matching: Python 3.12.6
</span></span><span style="display:flex;"><span>Uninstalled Python 3.12.6 in 300ms
</span></span><span style="display:flex;"><span> - cpython-3.12.6-macos-aarch64-none
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ uv run --no-python-downloads --python 3.12.6 python --version
</span></span><span style="display:flex;"><span>error: No interpreter found for Python 3.12.6 in virtual environments, managed installations, or search path
</span></span></code></pre></div> ]]></content:encoded>
    </item>
    
    <item>
      <title>Taskfile bash completion in homebrew</title>
      <link>https://blog.sandipb.net/2024/10/20/taskfile-bash-completion-in-homebrew/</link>
      <pubDate>Sun, 20 Oct 2024 23:19:31 -0400</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2024/10/20/taskfile-bash-completion-in-homebrew/</guid>
      <description><![CDATA[<p>A tip for anyone using <a href="https://taskfile.dev">Taskfile</a> and <a href="https://brew.sh">Homebrew</a> on macOS. Actually this is more
of a note to self, because I didn&rsquo;t pay attention to this earlier.</p>
<p>Homebrew has two versions of bash-completion: <code>bash-completion</code> and <code>bash-completion@2</code>. The former is meant for bash
3.x and the latter is for bash 4+.</p>
<p>Normally it hasn&rsquo;t mattered to me till now, because most formulas set up compatible completion scripts.</p>
<p>But it seems <a href="https://github.com/go-task/task/issues/862">Taskfile&rsquo;s completion script is only compatible with <code>bash-completion@2</code></a>.</p>]]></description>
      <content:encoded><![CDATA[      <p>A tip for anyone using <a href="https://taskfile.dev">Taskfile</a> and <a href="https://brew.sh">Homebrew</a> on macOS. Actually this is more
of a note to self, because I didn&rsquo;t pay attention to this earlier.</p>
<p>Homebrew has two versions of bash-completion: <code>bash-completion</code> and <code>bash-completion@2</code>. The former is meant for bash
3.x and the latter is for bash 4+.</p>
<p>Normally it hasn&rsquo;t mattered to me till now, because most formulas set up compatible completion scripts.</p>
<p>But it seems <a href="https://github.com/go-task/task/issues/862">Taskfile&rsquo;s completion script is only compatible with <code>bash-completion@2</code></a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Using an SSH jump host for Ansible</title>
      <link>https://blog.sandipb.net/2024/10/17/using-an-ssh-jump-host-for-ansible/</link>
      <pubDate>Thu, 17 Oct 2024 12:49:31 -0400</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2024/10/17/using-an-ssh-jump-host-for-ansible/</guid>
      <description><![CDATA[<p>I was working from outside home today, trying to push out changes to a bunch of my homelab servers. As usual I was using
Ansible, but I was connected over <a href="https://tailscale.com/">tailscale</a> to the home network.</p>
<p>Now normally I would just create a socks/http proxy to one of my home machines and set the proxy environment variable
like <code>HTTP_PROXY</code> and most apps would just work. But Ansible doesn&rsquo;t seem to respect that environment variable.</p>
<p>There is an <code>environment</code> keyword <a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_environment.html">that lets you set http_proxy
variables</a>, but that is for tasks
executing remotely. They can use that environment variable for commands they are executing which need to call over the Internet. But what we need is a way to reach the target host in the first place.</p>]]></description>
      <content:encoded><![CDATA[      <p>I was working from outside home today, trying to push out changes to a bunch of my homelab servers. As usual I was using
Ansible, but I was connected over <a href="https://tailscale.com/">tailscale</a> to the home network.</p>
<p>Now normally I would just create a socks/http proxy to one of my home machines and set the proxy environment variable
like <code>HTTP_PROXY</code> and most apps would just work. But Ansible doesn&rsquo;t seem to respect that environment variable.</p>
<p>There is an <code>environment</code> keyword <a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_environment.html">that lets you set http_proxy
variables</a>, but that is for tasks
executing remotely. They can use that environment variable for commands they are executing which need to call over the Internet. But what we need is a way to reach the target host in the first place.</p>
<p>Jeff Geerling has <a href="https://www.jeffgeerling.com/blog/2022/using-ansible-playbook-ssh-bastion-jump-host">a post about using ssh args</a> in <code>ansible.cfg</code> which looked to be exactly what I need.</p>
<p>But then I discovered the <code>-J</code> jump host parameter, which for some reason I had never spotted before all these years.</p>
<!-- markdownlint-disable-next-line -->

<div class="aside-note" >
	<div class="aside-text" ><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>-J destination
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Connect to the target host by first making an ssh connection to the jump host
</span></span><span style="display:flex;"><span>described by destination and then establishing a TCP forwarding to the
</span></span><span style="display:flex;"><span>ultimate destination from there.  Multiple jump hops may be specified
</span></span><span style="display:flex;"><span>separated by comma characters.  This is a shortcut to specify a ProxyJump
</span></span><span style="display:flex;"><span>configuration directive.  Note that configuration directives supplied on the
</span></span><span style="display:flex;"><span>command-line generally apply to the destination host and not any specified
</span></span><span style="display:flex;"><span>jump hosts.  Use `~/.ssh/config` to specify configuration for jump hosts.
</span></span></code></pre></div></div>
</div>

<p>So to actually use this, I had to add the following to <code>ansible.cfg</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#75715e"># For tailscale</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[ssh_connection]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ssh_args</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">-J guardian.rolling-doe.ts.net</span>
</span></span></code></pre></div><p>Here <code>guardian.rolling-doe.ts.net</code> is the (fictional) Tailscale hostname of the host I am trying to use as a bastion host.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>AI running out of original human generated content</title>
      <link>https://blog.sandipb.net/2024/06/10/ai-running-out-of-original-human-generated-content/</link>
      <pubDate>Mon, 10 Jun 2024 16:13:20 -0400</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2024/06/10/ai-running-out-of-original-human-generated-content/</guid>
      <description><![CDATA[<p><a href="https://fortune.com/2024/06/06/ai-training-bottleneck-google-meta-openai/">The AI gold rush is hitting a ‘bottleneck’ that could spell disaster for Google and Meta</a></p>



  <blockquote>
    <p>A new study released Thursday by research group Epoch AI projects that tech companies will exhaust the supply of
publicly available training data for AI language models by roughly the turn of the decade—sometime between 2026 and
2032.
&hellip;</p>
<p>In the longer term, there won’t be enough new blogs, news articles and social media commentary to sustain the current
trajectory of AI development, putting pressure on companies to tap into sensitive data now considered private—such as
emails or text messages—or relying on less-reliable “synthetic data” spit out by the chatbots themselves.</p>

  </blockquote>

<p>As more and more slop keeps getting churned out on the internet and floods our search results, there is little and little incentive anyway for humans to generate content in the first place. This data doom feels like a self-fulfilling prophecy.</p>]]></description>
      <content:encoded><![CDATA[      <p><a href="https://fortune.com/2024/06/06/ai-training-bottleneck-google-meta-openai/">The AI gold rush is hitting a ‘bottleneck’ that could spell disaster for Google and Meta</a></p>



  <blockquote>
    <p>A new study released Thursday by research group Epoch AI projects that tech companies will exhaust the supply of
publicly available training data for AI language models by roughly the turn of the decade—sometime between 2026 and
2032.
&hellip;</p>
<p>In the longer term, there won’t be enough new blogs, news articles and social media commentary to sustain the current
trajectory of AI development, putting pressure on companies to tap into sensitive data now considered private—such as
emails or text messages—or relying on less-reliable “synthetic data” spit out by the chatbots themselves.</p>

  </blockquote>

<p>As more and more slop keeps getting churned out on the internet and floods our search results, there is little and little incentive anyway for humans to generate content in the first place. This data doom feels like a self-fulfilling prophecy.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Using the code cli in VS Code Remote extension</title>
      <link>https://blog.sandipb.net/2024/01/17/using-the-code-cli-in-vs-code-remote-extension/</link>
      <pubDate>Wed, 17 Jan 2024 08:48:07 -0500</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2024/01/17/using-the-code-cli-in-vs-code-remote-extension/</guid>
      <description><![CDATA[<!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/dalle-vscode.png"
    alt="VS Code (Dall-e generated)">
</figure>

<p>One of the major reasons I switched to VS Code completely some years back is its excellent extension system, in
particular the seamless remote SSH editing extension.</p>
<h4 id="remote-ssh-extension-is-really-cool">Remote SSH extension is really cool!</h4>
<p>Using a local editor on a remote filesystem without fiddling with sshfs or the like, managing to use extensions like
Python, Go and Copilot, all setup and configured locally but running on content on a remote system has been pretty cool.
It helps me keep my office and personal content separate - I edit my personal codebase on my personal computers using
vscode on my office laptop when I need to.</p>
<h4 id="the-annoyance-about-the-code-cli">The annoyance about the code cli</h4>
<p>But one of the most annoying thing while using the VS Code remote ssh extension was the VS Code cli, which I use a lot.
I spend a lot of time on the terminal inside VS Code, and sometimes it is just easier to open a file from the command
line using <code>code FILE</code> instead of reaching for the mouse to click on the file in the explorer.</p>
<p>Here is where it gets tricky.</p>
<p>If you have a VS Code installation on the remote computer, and you used it to install the CLI, that CLI executable  (On
Mac installed in <code>/usr/local/bin/code</code> ) will always open files in that local VS Code installation. It will not open the
file in the remote SSH VS Code workspace that you have open.</p>
<p><em>TLDR: Use the script at the end of this post instead of the code cli in path if you want to open files in the remote ssh extension workspace.</em></p>]]></description>
      <content:encoded><![CDATA[      <!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/dalle-vscode.png"
    alt="VS Code (Dall-e generated)">
</figure>

<p>One of the major reasons I switched to VS Code completely some years back is its excellent extension system, in
particular the seamless remote SSH editing extension.</p>
<h4 id="remote-ssh-extension-is-really-cool">Remote SSH extension is really cool!</h4>
<p>Using a local editor on a remote filesystem without fiddling with sshfs or the like, managing to use extensions like
Python, Go and Copilot, all setup and configured locally but running on content on a remote system has been pretty cool.
It helps me keep my office and personal content separate - I edit my personal codebase on my personal computers using
vscode on my office laptop when I need to.</p>
<h4 id="the-annoyance-about-the-code-cli">The annoyance about the code cli</h4>
<p>But one of the most annoying thing while using the VS Code remote ssh extension was the VS Code cli, which I use a lot.
I spend a lot of time on the terminal inside VS Code, and sometimes it is just easier to open a file from the command
line using <code>code FILE</code> instead of reaching for the mouse to click on the file in the explorer.</p>
<p>Here is where it gets tricky.</p>
<p>If you have a VS Code installation on the remote computer, and you used it to install the CLI, that CLI executable  (On
Mac installed in <code>/usr/local/bin/code</code> ) will always open files in that local VS Code installation. It will not open the
file in the remote SSH VS Code workspace that you have open.</p>
<p><em>TLDR: Use the script at the end of this post instead of the code cli in path if you want to open files in the remote ssh extension workspace.</em></p>
<p>This has something to do with how the CLI discovers the &ldquo;server&rdquo; instance - the VS Code instance that is running on
machine and which hosts the editor. The communication happens via a socket, and the path to the socket is discovered
using a combination of the CLI path and environment variables.</p>
<p>So if you use the <code>code</code> cli in the terminal of the remote SSH workspace, it will open the file in the local VS Code.</p>
<p>Incidentally, the remote SSH extension installs a VS Code installation on the remote server in the home directory, and it has its own cli executable. <em>If you use this cli instead of the one in the path, it will discover the remote ssh workspace correctly and open the file in it.</em></p>
<p>So all we have to do is to discover this cli path correctly and use it instead of the one in the path. I found a
suitable environment variable set by the core Git extension which can be used to find this. I used the bash function
below in my bashrc to add a command (<code>code_remote</code>) that I now use instead for opening files in the terminal of remote
SSH extension workspaces.</p>
<script src="https://gist.github.com/sandipb/f944e052b8699aac8708528fb8fcd3d0.js?file=code_remote.sh"></script> ]]></content:encoded>
    </item>
    
    <item>
      <title>Using Giscus for commenting on this blog</title>
      <link>https://blog.sandipb.net/2024/01/02/using-giscus-for-commenting-on-this-blog/</link>
      <pubDate>Tue, 02 Jan 2024 17:19:07 -0500</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2024/01/02/using-giscus-for-commenting-on-this-blog/</guid>
      <description><![CDATA[<!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/2024/01/article.png"
    alt="An online article (Dall-E 3)">
</figure>

<p>I had removed disqus comments from my blog <a href="/2021/10/29/disabling-comments/">a few years back</a> when I noticed the
horrible privacy issues with Disqus.</p>
<p>But I always felt that I was missing out on some feedback I had received from visitors in the past, especially when I
would benefit from correcting my mistakes.</p>]]></description>
      <content:encoded><![CDATA[      <!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/2024/01/article.png"
    alt="An online article (Dall-E 3)">
</figure>

<p>I had removed disqus comments from my blog <a href="/2021/10/29/disabling-comments/">a few years back</a> when I noticed the
horrible privacy issues with Disqus.</p>
<p>But I always felt that I was missing out on some feedback I had received from visitors in the past, especially when I
would benefit from correcting my mistakes.</p>
<p><a href="https://cdwilson.dev/articles/using-giscus-for-comments-in-hugo/">This article from Chris Wilson</a> resonated with me.</p>
<p>I wanted:</p>
<ol>
<li>Feedback</li>
<li>Didn&rsquo;t want to spend money on a low traffic, zero commercialized blog.</li>
<li>Some friction for commenters by requiring identity to reduce comment spam.</li>
<li>Relatively low privacy impact.</li>
</ol>
<p>Github fits those requirements. Most developers already use Github to some extent for either their own source control or
interacting with other software hosted there. I am aware of the criticism of Github and I use
<a href="https://about.gitea.com/">Gitea</a> at home myself (big fan). But what is important to me is to reduce 1000 cuts of cloud
subscription for hosting it myself.</p>
<p>So, like Chris, I decided to install <a href="https://giscus.app/">giscus</a> for comments on this website. Feel free to use the
comments  for this post to test out how it works - I am ok with unrelated comments for this post as long as it is no
spam/promotion of <em>any</em> kind.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Age encryption cookbook</title>
      <link>https://blog.sandipb.net/2023/07/06/age-encryption-cookbook/</link>
      <pubDate>Thu, 06 Jul 2023 23:42:26 -0400</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2023/07/06/age-encryption-cookbook/</guid>
      <description><![CDATA[<!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/2023/05/dalle-e-2023-07-06-encryption.png"
    alt="Many locks for one secret">
</figure>

<p>One of the niftiest tools that I have been using a lot nowadays is the deceptively simple encryption/decryption
command line tool called <a href="https://github.com/FiloSottile/age"><code>age</code></a> by <a href="https://filippo.io/">Filippo Valsorda</a>.</p>
<p>Encrypting secrets using keypairs instead of a single passphrase is obviously a more secure option, as it separates the concerns for encryption and decryption. Therefore you can share around the public key without fear for others to encrypt data for you that only you can decrypt.</p>
<p>Traditionally, we have been using tools like <a href="https://gnupg.org/">gpg</a> e.g. while using eyaml in Puppet, etc. However,
gpg comes with its own overhead of key management - multiple keyrings, tool configuration, etc. which makes the use case
of simple encryption to be fairly complicated.</p>
<p><code>age</code> packs a lot of functionality into its dead simple CLI interface - a single file to &ldquo;manage&rdquo; the keypair. It
requires zero configuration. More importantly, it supports not just its own
<a href="https://en.wikipedia.org/wiki/Curve25519">X25519</a>-based key pair format, but it can even use SSH key pairs for
encryption. For simple use cases, it even supports symmetric passphrases.</p>
<p>I have been keeping this document in my PKM for a while, and I thought it best to share in public as a cookbook as well.</p>]]></description>
      <content:encoded><![CDATA[      <!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/2023/05/dalle-e-2023-07-06-encryption.png"
    alt="Many locks for one secret">
</figure>

<p>One of the niftiest tools that I have been using a lot nowadays is the deceptively simple encryption/decryption
command line tool called <a href="https://github.com/FiloSottile/age"><code>age</code></a> by <a href="https://filippo.io/">Filippo Valsorda</a>.</p>
<p>Encrypting secrets using keypairs instead of a single passphrase is obviously a more secure option, as it separates the concerns for encryption and decryption. Therefore you can share around the public key without fear for others to encrypt data for you that only you can decrypt.</p>
<p>Traditionally, we have been using tools like <a href="https://gnupg.org/">gpg</a> e.g. while using eyaml in Puppet, etc. However,
gpg comes with its own overhead of key management - multiple keyrings, tool configuration, etc. which makes the use case
of simple encryption to be fairly complicated.</p>
<p><code>age</code> packs a lot of functionality into its dead simple CLI interface - a single file to &ldquo;manage&rdquo; the keypair. It
requires zero configuration. More importantly, it supports not just its own
<a href="https://en.wikipedia.org/wiki/Curve25519">X25519</a>-based key pair format, but it can even use SSH key pairs for
encryption. For simple use cases, it even supports symmetric passphrases.</p>
<p>I have been keeping this document in my PKM for a while, and I thought it best to share in public as a cookbook as well.</p>
<ul>
<li><a href="#installation">Installation</a></li>
<li><a href="#usage">Usage</a>
<ul>
<li><a href="#key-generation">Key generation</a></li>
<li><a href="#encryption">Encryption</a></li>
<li><a href="#decryption">Decryption</a></li>
<li><a href="#identity-encryption">Identity encryption</a></li>
<li><a href="#using-ssh-keys-for-encryption">Using SSH keys for encryption</a></li>
<li><a href="#using-a-simple-password-for-encryption-instead-of-a-key-pair">Using a simple password for encryption instead of a key pair</a></li>
</ul>
</li>
<li><a href="#other-uses">Other uses</a></li>
</ul>
<h2 id="installation">Installation</h2>
<p>I mostly work on Mac nowadays, and prefer to use <a href="https://brew.sh/">homebrew</a> for installing age. Instructions for other
platforms are in the official docs.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ brew install age
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><h2 id="usage">Usage</h2>
<h3 id="key-generation">Key generation</h3>
<p>Before you can accept encrypted content, you need to generate your keypair. Key generation gives us a <em>public key</em> and a
<em>private key</em>. The public key is also referred to as a <em>recipient</em> in the tool documentation.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ age-keygen
</span></span><span style="display:flex;"><span># created: 2023-07-06T22:35:40-04:00
</span></span><span style="display:flex;"><span># public key: age1lm45u4v4nka4s3uv68dpfv2c33fdgak6mjvdn5sfsv8rlvss9vyss927mg
</span></span><span style="display:flex;"><span>AGE-SECRET-KEY-167YP7YLNUE74Y7PA3X9SKXCS92JJ2DQCVN584WV20Y9WW5VL67ZSFFLDZ4
</span></span></code></pre></div><p>Specifying a file to save, using <code>-o</code>, will store the previously shown output to the file. But it hides the secret key from being displayed to the console. It still displays the public part.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ age-keygen -o key.txt
</span></span><span style="display:flex;"><span>Public key: age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cat key.txt
</span></span><span style="display:flex;"><span># created: 2023-07-06T22:39:59-04:00
</span></span><span style="display:flex;"><span># public key: age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t
</span></span><span style="display:flex;"><span>AGE-SECRET-KEY-1FP43QXCQ3VM4EJANLG3DJ0VRHZD9FY0HW4RM5WVP9JNPZPVARD4SGAXD94
</span></span></code></pre></div><p>You can extract the public key from a key file using <code>age-keygen -y</code>, similar to how you might have used <code>ssh-keygen -y</code>
for ssh keys.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ age-keygen -y key.txt
</span></span><span style="display:flex;"><span>age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t
</span></span></code></pre></div><p>However, unlike <code>ssh-keygen</code>, you can extract public keys from a bunch of age identity files in one shot.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ cat key*.txt | age-keygen -y
</span></span><span style="display:flex;"><span>age1sa3srvuxwx4g4sshgxvgc0cx8c5kwukv5kmvsmnp3d43levctehsla3m2f
</span></span><span style="display:flex;"><span>age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t
</span></span></code></pre></div><h3 id="encryption">Encryption</h3>
<p>You encrypt using a recipient name. The output is by default in an efficient binary form. You can use the <code>-a</code> option to
<em>armor</em> the output into a printable version.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ cat /etc/hosts | age -r age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t -o hosts-1.age
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ file hosts-1.age
</span></span><span style="display:flex;"><span>hosts-1.age: data
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cat /etc/hosts | age -r age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t -a
</span></span><span style="display:flex;"><span>-----BEGIN AGE ENCRYPTED FILE-----
</span></span><span style="display:flex;"><span>YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKdXpDQURkeVUzNmxKdk9X
</span></span><span style="display:flex;"><span>ckgvS2FYZ2laRjlCZzUvV3IwYzMwcGNpakY0Cm1WeC9DakFFMUZjWGdqalYweHB3
</span></span><span style="display:flex;"><span>MkppYlV3YXhNenpxSlg4MEhsV1R2ckEKLS0tIFQvcjMvVFZLM1UrRWlRYTVNenFl
</span></span><span style="display:flex;"><span>WU93bFcwT011dFpLektWTnJzZkFoNVUKNd5jWDu9jzly/x9NPRFwTiTSPDrSn5A3
</span></span><span style="display:flex;"><span>8UJ6sPtY4s2z6rlH6v2di3/PYympUNsJFlYIVchOor8YhivI80JWOZyMdge2h6Qu
</span></span><span style="display:flex;"><span>49C+pEftVeyqYcaAhusgcQREWgXLp53GbpLbsWst4tiXFE5Hku4M5nLGIMrPjDw7
</span></span><span style="display:flex;"><span>bPR+/9GFLVyBEAF27iwFGpFsR4ywKzgwgbLWJBvzZfv13/g6GDu8XAmMOUc3aGr9
</span></span><span style="display:flex;"><span>7oUq6SNNmHWhpVImZc/h2a73LVPZqmXj515jx0vIYaBsaZMEMPFybiIETrNXTuQO
</span></span><span style="display:flex;"><span>UEl6WrDniCtfxz9hvNMt4kkRoj2F8ebv/yYK+tc=
</span></span><span style="display:flex;"><span>-----END AGE ENCRYPTED FILE-----
</span></span></code></pre></div><p>Repeated encryption of the same file for the same recipient will give different output. Good thing to keep in mind if
you were planning on using file hashes to check if the encrypted files have been generated by the same key. You
shouldn&rsquo;t.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ cat /etc/hosts | age -r age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t -o hosts-2.age
</span></span><span style="display:flex;"><span>$ diff -u hosts-1.age hosts-2.age
</span></span><span style="display:flex;"><span>Binary files hosts-1.age and hosts-2.age differ
</span></span></code></pre></div><p>You can specify multiple recipients by repeating the <code>-r</code> parameter, or put them all in a recipients file and specifying
the file using <code>-R</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ age-keygen -o key-alt.txt
</span></span><span style="display:flex;"><span>Public key: age1sa3srvuxwx4g4sshgxvgc0cx8c5kwukv5kmvsmnp3d43levctehsla3m2f
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cat key*.txt | age-keygen -y &gt; recipients.txt
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cat recipients.txt
</span></span><span style="display:flex;"><span>age1sa3srvuxwx4g4sshgxvgc0cx8c5kwukv5kmvsmnp3d43levctehsla3m2f
</span></span><span style="display:flex;"><span>age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cat /etc/hosts | age -R recipients.txt -o hosts-3.age
</span></span></code></pre></div><p>The same encrypted file can be decrypted by both the recipients. That is, you don&rsquo;t have to generate different encrypted versions for each recipient.</p>
<p>The recipients file format supports comments, so you can document the recipient keys.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ cat recipients.txt
</span></span><span style="display:flex;"><span># Build key
</span></span><span style="display:flex;"><span>age1sa3srvuxwx4g4sshgxvgc0cx8c5kwukv5kmvsmnp3d43levctehsla3m2f
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span># QA key
</span></span><span style="display:flex;"><span>age1l36wdcfx825h727h5wjs7swh675f3jd4qde0zj47gyslmuja3agspge22t
</span></span></code></pre></div><p>You can mix SSH public keys and age public keys in the recipients file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ cat ~/.ssh/id_github.pub ~/.ssh/id_rsa.pub &gt; r.txt
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ age-keygen -y ~/.age-chezmoi.txt &gt;&gt; r.txt
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ age -R r.txt -a &lt; /etc/hosts
</span></span><span style="display:flex;"><span>-----BEGIN AGE ENCRYPTED FILE-----
</span></span><span style="display:flex;"><span>YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1yc2EgU0wyUlJBCk5OS2QzSzRl
</span></span><span style="display:flex;"><span>ZCtVUmJxMXgwVVRBNjFJWU00emxwbmRFbmlsWDRCUlhwNjF2WEJoZUJtZysxSEVm
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><h3 id="decryption">Decryption</h3>
<p>Here the same file encrypted for both the recipients can be decrypted with their respective private keys.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ age -d -i key.txt hosts-3.age
</span></span><span style="display:flex;"><span>#<span style="color:#75715e">#</span>
</span></span><span style="display:flex;"><span># Host Database
</span></span><span style="display:flex;"><span>#
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ age -d -i key-alt.txt hosts-3.age
</span></span><span style="display:flex;"><span>#<span style="color:#75715e">#</span>
</span></span><span style="display:flex;"><span># Host Database
</span></span><span style="display:flex;"><span>#
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><h3 id="identity-encryption">Identity encryption</h3>
<p>Normally the encrypted file can only be decrypted by the recipient public key. Even you cannot decrypt it afterwards.</p>
<p>You can specify your own public key as recipient to work around it. Or you can just specify your key file (using <code>-i</code>),
containing your private key, for an identity encryption.</p>
<p>Here the file is being encrypted for a different recipient, as well as for the person having a private key locally.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ age -e -i key.txt -r <span style="color:#66d9ef">$(</span>age-keygen -y key-alt.txt<span style="color:#66d9ef">)</span> -a /etc/hosts | age -d -i key.txt
</span></span><span style="display:flex;"><span>#<span style="color:#75715e">#</span>
</span></span><span style="display:flex;"><span># Host Database
</span></span><span style="display:flex;"><span>#
</span></span></code></pre></div><div class="aside-note" >
	<div class="aside-text" >To use the <code>-i</code> parameter, you have to explicitly use the <code>-e</code> or <code>--encrypt</code> parameter to specify you are trying to do
encryption.</div>
</div>
<h3 id="using-ssh-keys-for-encryption">Using SSH keys for encryption</h3>
<p>New versions of <code>age</code> can also use your existing SSH key pairs for encryption/decryption.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ cat /etc/hosts | age -R ~/.ssh/id_rsa.pub -o hosts-4.age
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ age -d -i ~/.ssh/id_rsa hosts-4.age
</span></span><span style="display:flex;"><span>#<span style="color:#75715e">#</span>
</span></span><span style="display:flex;"><span># Host Database
</span></span><span style="display:flex;"><span>#
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>While doing decryption, if your SSH private key was encrypted, you will be asked for the passphrase.</p>
<h3 id="using-a-simple-password-for-encryption-instead-of-a-key-pair">Using a simple password for encryption instead of a key pair</h3>
<p>For even simpler use cases, age also supports simple passphrase based encryption.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ cat /etc/hosts | age -p -o hosts-5.age
</span></span><span style="display:flex;"><span>Enter passphrase (leave empty to autogenerate a secure one):
</span></span><span style="display:flex;"><span>Confirm passphrase:
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ age -d hosts-5.age
</span></span><span style="display:flex;"><span>Enter passphrase:
</span></span><span style="display:flex;"><span>#<span style="color:#75715e">#</span>
</span></span><span style="display:flex;"><span># Host Database
</span></span><span style="display:flex;"><span>#
</span></span></code></pre></div><h2 id="other-uses">Other uses</h2>
<p>age is a simple data encryption/decryption tool. It is not aware of file formats. So if you wanted to encrypt just a
single key value in an YAML file, you will need to extract the value from the file, provide it to age for encryption, and then add the (probably armored) value back to the file.</p>
<p>Higher level tools like <a href="https://github.com/getsops/sops">Mozilla SOPS</a> understand file structure of formats like YAML,
JSON etc. and can use age for encryption/decryption for individual values in these data structures.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Converting audio to text from command line using commercial and opensource tools</title>
      <link>https://blog.sandipb.net/2023/05/24/converting-audio-to-text-from-command-line-using-commercial-and-opensource-tools/</link>
      <pubDate>Wed, 24 May 2023 18:10:57 -0400</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2023/05/24/converting-audio-to-text-from-command-line-using-commercial-and-opensource-tools/</guid>
      <description><![CDATA[<!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/2023/05/speaking-in-mic.png"
    alt="Girl speaking in Mic, by Dall-E">
</figure>

<p>For a while now, I have been wanting to see the options I have today to convert speech to text at the command
line. Lots of commercial options exist today for doing that, but I also wanted to check out open source options.</p>
<p>In this experiment, I decided to compare:</p>
<ul>
<li>An opensource option: <a href="https://github.com/petewarden/spchcat">spchcat</a></li>
<li>A commercial option - <a href="https://cloud.google.com/speech-to-text">Google Cloud&rsquo;s ML Speech API</a></li>
</ul>]]></description>
      <content:encoded><![CDATA[      <!-- markdownlint-disable-next-line -->
<figure class="alignright"><img src="/images/2023/05/speaking-in-mic.png"
    alt="Girl speaking in Mic, by Dall-E">
</figure>

<p>For a while now, I have been wanting to see the options I have today to convert speech to text at the command
line. Lots of commercial options exist today for doing that, but I also wanted to check out open source options.</p>
<p>In this experiment, I decided to compare:</p>
<ul>
<li>An opensource option: <a href="https://github.com/petewarden/spchcat">spchcat</a></li>
<li>A commercial option - <a href="https://cloud.google.com/speech-to-text">Google Cloud&rsquo;s ML Speech API</a></li>
</ul>
<!-- markdownlint-disable-next-line -->
<p><strong>Table of Contents</strong></p>
<ul>
<li><a href="#spchcat">spchcat</a></li>
<li><a href="#google-clouds-ml-speech-api">Google Cloud&rsquo;s ML Speech API</a></li>
<li><a href="#audio-samples-that-i-tried-out">Audio samples that I tried out</a>
<ul>
<li><a href="#sample-one-from-kaggle">Sample one from Kaggle</a></li>
<li><a href="#sample-two-from-a-speech-by-gandhi">Sample two from a speech by Gandhi</a></li>
</ul>
</li>
<li><a href="#prepping-the-audio-samples">Prepping the audio samples</a></li>
<li><a href="#trying-out-the-first-sample">Trying out the first sample</a></li>
<li><a href="#trying-out-the-second-sample">Trying out the second sample</a></li>
<li><a href="#punctuation-in-transcription">Punctuation in transcription</a></li>
<li><a href="#conclusion">Conclusion</a></li>
<li><a href="#references">References</a></li>
</ul>
<h2 id="spchcat">spchcat</h2>
<p><a href="https://github.com/petewarden/spchcat"><code>spchcat</code></a> describes itself as:</p>



  <blockquote>
    <p><em>spchcat</em> is a command-line tool that reads in audio from .WAV files, a microphone, or system audio inputs and
converts any speech found into text.</p>
<p>It runs locally on your machine, with no web API calls or network activity, and is open source.</p>
<p>It is built on top of Coqui&rsquo;s speech to text library, TensorFlow, KenLM, and data from Mozilla&rsquo;s Common Voice project.</p>

  </blockquote>

<p>It seems to be written in C, and only has pre-built debian packages for amd64 and arm (for Raspberry pi).</p>
<p>Interestingly, it can stream convert audio from a microphone to text, which lets us do creative real time transcription
applications.</p>
<h2 id="google-clouds-ml-speech-api">Google Cloud&rsquo;s ML Speech API</h2>
<p><a href="https://cloud.google.com/speech-to-text">Google Cloud&rsquo;s ML Speech API</a> lets you use the <code>gcloud</code> CLI tool to transcribe audio from the command line. It has streaming abilities as well.</p>
<p>It is pretty useful for anybody to play around with speech-to-text without paying, as it has generous enough free limits
for dabbling.</p>



  <blockquote>
    <p>New customers get $300 in free credits to spend on Speech-to-Text. All customers get 60 minutes for transcribing and
analyzing audio free per month, not charged against your credits.</p>

  </blockquote>

<h2 id="audio-samples-that-i-tried-out">Audio samples that I tried out</h2>
<p>I used two different audio samples to compare the tools.</p>
<h3 id="sample-one-from-kaggle">Sample one from Kaggle</h3>
<p>The first one is a short (18s) audio sample from a Kaggle data set. They seem to be<a href="https://www.cs.columbia.edu/~hgs/audio/harvard.html"> <em>Harvard Sentences</em></a>, used in speech quality measurements. [<a href="https://www.kaggle.com/datasets/pavanelisetty/sample-audio-files-for-speech-recognition?resource=download">Src</a>]</p>
<p>Actual transcript:</p>



  <blockquote>
    <p>The stale smell of old beer lingers. It takes heat to bring out the odor. A cold dip restores health and zest. A salt
pickle tastes fine with ham. Tacos Al Pastor are my favorite. A zestful food is the hot-cross bun.</p>

  </blockquote>

<h3 id="sample-two-from-a-speech-by-gandhi">Sample two from a speech by Gandhi</h3>
<p>I also wanted to test how well these two software do with non-native English accents. For this, I picked up the audio from <a href="https://www.ndtv.com/india-news/mahatma-gandhis-famous-speech-at-kingsley-hall-in-1931-565204">Mahatma Gandhi&rsquo;s speech at Kingsley Hall</a> in London, on October 1931. [<a href="https://commons.wikimedia.org/wiki/File:Gandhi_-_His_Spiritual_Message_to_the_World,_17_October_1931.mp3">wikimedia src</a>]</p>
<p>Actual transcript of the first 60 seconds of the audio (reason explained later in the post):</p>



  <blockquote>
    <p>There is an indefinable mysterious power that pervades everything. I feel it though I do not see it. It is this
unseen power which makes itself felt and yet defies all proof, because it is so unlike all that I perceive through
my senses. It transcends the senses. But it is possible to reason out the existence of God to a limited extent.
Even in ordinary affairs we know that people do not know who rules, or why, and how He rules and yet they know
that there is a power that certainly rules.</p>
<p>In my tour last year in Mysore I met many poor villagers and I found upon inquiry that they did not know who ruled
Mysore. They <strong>[t58]</strong> simply said some <strong>[t60]</strong> God ruled it. &hellip;. (<em>full text in the link below</em>)</p>
<p><cite>&ndash; <a href="https://www.ndtv.com/india-news/mahatma-gandhis-famous-speech-at-kingsley-hall-in-1931-565204">Mahatma Gandhi&rsquo;s famous speech at Kingsley Hall in 1931</a></cite></p>

  </blockquote>

<h2 id="prepping-the-audio-samples">Prepping the audio samples</h2>
<p>Both the apps didn&rsquo;t like the mp3 format audio that I had. WAV files are universally supported. This made me convert the
audio format using <a href="https://ffmpeg.org/"><code>ffmpeg</code></a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ ffmpeg -i harvard.mp3 harvard.wav
</span></span></code></pre></div><p>But gcloud also refused to process stereo audio.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ gcloud ml speech recognize harvard.wav  --language-code<span style="color:#f92672">=</span>en-US
</span></span><span style="display:flex;"><span>ERROR: (gcloud.ml.speech.recognize) INVALID_ARGUMENT: Must use single channel (mono) audio, but WAV header indicates 2 channels.
</span></span></code></pre></div><p>So I had to convert the audio to mono adding the <code>-ac 1</code> parameter to ffmpeg</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>ffmpeg -i harvard.mp3 -ac 1 harvard.wav
</span></span></code></pre></div><p>For the Gandhi speech, gcloud also asked for extra configuration for audio longer than 60 seconds.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ gcloud ml speech recognize gandhi.wav    --language-code<span style="color:#f92672">=</span>en-US
</span></span><span style="display:flex;"><span>ERROR: (gcloud.ml.speech.recognize) INVALID_ARGUMENT: Sync input too long. For audio longer than 1 min use LongRunningRecognize with a &#39;uri&#39; parameter.
</span></span></code></pre></div><p>So I truncated the audio to 60 seconds using ffmpeg.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ ffmpeg -i gandhi.mp3 -t <span style="color:#ae81ff">60</span> -ac <span style="color:#ae81ff">1</span> gandhi_t60.wav
</span></span></code></pre></div><p>Weirdly, gcloud still complained of the length of the audio. So I finally truncated it to 58 seconds, and that worked.</p>
<h2 id="trying-out-the-first-sample">Trying out the first sample</h2>
<p>This was a short speech by a native English speaker, speaking kind of slowly and carefully, presumably for clarity.</p>
<p><code>spchcat</code> handled most of it correctly. But completely truncated the last spoken sentence. I am not sure if this is a misconfiguration at my end or a problem with the software.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ spchcat ./harvard.wav
</span></span><span style="display:flex;"><span>TensorFlow: v2.3.0-14-g4bdd3955115
</span></span><span style="display:flex;"><span> Coqui STT: v1.1.0-0-gf3605e23
</span></span><span style="display:flex;"><span>rate: rate clipped 13 samples; decrease volume?
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>the stale smell of old bear lingers it takes heat to bring out the odor a cold dip restores health and zest a salt pickled taste fine with ham tackles all pastor are my favorite existlessness
</span></span></code></pre></div><p><code>glcoud</code> was completely on point.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">$</span> <span style="color:#960050;background-color:#1e0010">gcloud</span> <span style="color:#960050;background-color:#1e0010">ml</span> <span style="color:#960050;background-color:#1e0010">speech</span> <span style="color:#960050;background-color:#1e0010">recognize</span> <span style="color:#960050;background-color:#1e0010">harvard-mono.wav</span>   <span style="color:#960050;background-color:#1e0010">--language-code=en-US</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;requestId&#34;</span>: <span style="color:#e6db74">&#34;4451273191690268245&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;results&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;alternatives&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">&#34;confidence&#34;</span>: <span style="color:#ae81ff">0.9559944</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">&#34;transcript&#34;</span>: <span style="color:#e6db74">&#34;the stale smell of old beer lingers it takes heat to bring out the odor a cold dip restores health and zest a salt pickle taste fine with ham tacos al Pastore are my favorite a zestful food is the hot cross bun&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      ],
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;languageCode&#34;</span>: <span style="color:#e6db74">&#34;en-us&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;resultEndTime&#34;</span>: <span style="color:#e6db74">&#34;18s&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;totalBilledTime&#34;</span>: <span style="color:#e6db74">&#34;18s&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="aside-note" >
	<div class="aside-text" >Note, that in the rest of this post, I will be using <code>fold</code> to wrap the long lines and <code>jq</code> to just extract
the interesting text from json.</div>
</div>
<h2 id="trying-out-the-second-sample">Trying out the second sample</h2>
<p><code>spchcat</code> started well, and again completely botched it at the end. It makes no sense that this is a problem with its
speech detection. If I have to guess, it probably has got to do with stream buffering sync issues.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-console" data-lang="console"><span style="display:flex;"><span>$ spchcat ./gandhi_t60.wav | fold -s
</span></span><span style="display:flex;"><span>TensorFlow: v2.3.0-14-g4bdd3955115
</span></span><span style="display:flex;"><span> Coqui STT: v1.1.0-0-gf3605e23
</span></span><span style="display:flex;"><span>there is an indefinable mysterious power that pervades every thing i feel it
</span></span><span style="display:flex;"><span>though i do not see it it is this unseen power which makes itself felt and yet
</span></span><span style="display:flex;"><span>defies all proof because it is so unlike all that i perceive through my senses
</span></span><span style="display:flex;"><span>it transcends the senses but it is possible to reason out the existence of god
</span></span><span style="display:flex;"><span>to his relatedness know that people do not know who rose or hand how he does
</span></span><span style="display:flex;"><span>and yet they know that there is a power let certainement very poor pieni found
</span></span><span style="display:flex;"><span>upon inquiry that he did not know who ruled my sore the simple
</span></span></code></pre></div><p><code>gcloud</code> did also started reasonably well. But completely fudged it towards the end, missing whole words (&ldquo;Even in
ordinary affairs&rdquo;), and then completely mixing them up.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ gcloud ml speech recognize gandhi_t58.wav --language-code<span style="color:#f92672">=</span>en-US <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  | fold -s
</span></span><span style="display:flex;"><span>  &#34;there is an indefinable mysterious power that pervades everything I feel it so
</span></span><span style="display:flex;"><span>  I do not see it it is this unseen power which makes itself heard and yet
</span></span><span style="display:flex;"><span>  devised all proof because it is so unlike all that I perceive through my senses
</span></span><span style="display:flex;"><span>  it transcends the changes but it is possible to reason out the existence of God
</span></span><span style="display:flex;"><span>  to a limited extent we know that people do not know who rules or why and how he
</span></span><span style="display:flex;"><span>  rules and yet they know that there is a power that certainly who&#39;s in my tour
</span></span><span style="display:flex;"><span>  last year in My Sword I made many poor villagers and I found upon inquiry that
</span></span><span style="display:flex;"><span>  they did not know who ruled my soul&#34;
</span></span></code></pre></div><p>I tried some additional parameters to see if they fix the problems with <code>gcloud</code>:</p>
<ul>
<li>
<p>Providing an encoding rate (e.g. <code> --sample-rate 44100</code>) didn&rsquo;t make any difference.</p>
</li>
<li>
<p>Using the <code>latest_lang</code> model however, made a big difference in the accuracy:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ gcloud ml speech recognize gandhi_t58.wav <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --language-code=en-US --model latest_long \
</span></span><span style="display:flex;"><span>  | fold -s
</span></span><span style="display:flex;"><span>&#34;there is an indefinable mysterious power that pervades everything I feel it
</span></span><span style="display:flex;"><span>though I do not see it it is this unseen power which makes itself felt and yet
</span></span><span style="display:flex;"><span>defies all proof because it is so unlike all that I perceive through my senses
</span></span><span style="display:flex;"><span>it transcends the senses but it is possible to reason out the existence of God
</span></span><span style="display:flex;"><span>to a limited extent even in ordinary Affairs we know that people do not know
</span></span><span style="display:flex;"><span>who rules or why and how he rules and yet they know that there is a power that
</span></span><span style="display:flex;"><span>certainly rules in my tour last year in my soul I met many poor villagers and I
</span></span><span style="display:flex;"><span>found upon inquiry that they did not know who ruled my soul&#34;
</span></span></code></pre></div></li>
</ul>
<h2 id="punctuation-in-transcription">Punctuation in transcription</h2>
<p>All transcription seems bereft of any punctuation - sentences run into each other.</p>
<p>I tried the automatic punctuation feature in gcloud, and it sorta worked in the second sample. Even in the smaller sample with clear native speech, it didn&rsquo;t do so well.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ gcloud ml speech recognize harvard-mono.wav  --language-code<span style="color:#f92672">=</span>en-US <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --model latest_long --enable-automatic-punctuation \
</span></span><span style="display:flex;"><span>  | jq &#39;.results[].alternatives[].transcript&#39;| fold -s
</span></span><span style="display:flex;"><span>&#34;the stale smell of old beer lingers&#34;
</span></span><span style="display:flex;"><span>&#34; it takes heat to bring out the odor.&#34;
</span></span><span style="display:flex;"><span>&#34; A cold dip restores health and zest a salt pickle tastes fine with ham tacos
</span></span><span style="display:flex;"><span>al pastor are my favorite a zestful food is the hot cross bun.&#34;
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ gcloud ml speech recognize gandhi_t58.wav --language-code<span style="color:#f92672">=</span>en-US <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --model latest_long --enable-automatic-punctuation \
</span></span><span style="display:flex;"><span>  | jq &#39;.results[].alternatives[].transcript&#39;| fold -s
</span></span><span style="display:flex;"><span>&#34;There is an indefinable mysterious power that pervades everything. I feel it
</span></span><span style="display:flex;"><span>though. I do not see it it is this unseen power which makes itself felt and yet
</span></span><span style="display:flex;"><span>defies all proof because it is so unlike all that. I perceive through my
</span></span><span style="display:flex;"><span>senses. It transcends the senses but it is possible to reason out the existence
</span></span><span style="display:flex;"><span>of God to a limited extent even in ordinary Affairs. We know that people do not
</span></span><span style="display:flex;"><span>know who rules or why and how he rules and yet they know that there is a power
</span></span><span style="display:flex;"><span>that certainly rules in my tour last year in my soul. I met many poor villagers
</span></span><span style="display:flex;"><span>and I found upon inquiry that they did not know who ruled my soul.&#34;
</span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
<p>In my two tests, <code>gcloud</code> is definitely more accurate than <code>spchcat</code>. But just two audio samples are ridiculously small
data points to derive conclusions on these tools. Accuracy will definitely be improved with the accent, but also there
are many other tunables to explore with both these tools, that I haven&rsquo;t yet touched.</p>
<p>But given the cost factor of <code>gcloud</code>, playing around a lot for free with transcription is probably only feasible with
<code>spchcat</code>, so we should definitely not rule it out.</p>
<p>This is just my initial foray in this domain. I might revisit these samples after I understood some of the underlying theories better.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://petewarden.com/2022/01/06/launching-spchcat-an-open-source-speech-recognition-tool-for-linux-and-raspberry-pi/">Blog post by the author of <code>spchcat</code> annoucing the tool</a></li>
<li><a href="https://www.hackster.io/petewarden/recognizing-speech-with-a-raspberry-pi-50b0e6">Recognizing Speech with a Raspberry Pi</a>: A demo of doing real time speech transcription on a Raspberry PI using <code>spchcat</code></li>
</ul> ]]></content:encoded>
    </item>
    
    <item>
      <title>Decrypting and concatenating PDFs with qpdf</title>
      <link>https://blog.sandipb.net/2023/05/09/decrypting-and-concatenating-pdfs-with-qpdf/</link>
      <pubDate>Tue, 09 May 2023 14:25:48 -0400</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2023/05/09/decrypting-and-concatenating-pdfs-with-qpdf/</guid>
      <description><![CDATA[<!-- markdownlint-disable-next-line -->
<figure class="alignright"><a href="https://qpdf.readthedocs.io/"><img src="/images/2023/05/qpdf.svg"
    alt="qpdf docs"></a>
</figure>

<p>I wanted to quickly jot down some of the PDF tasks that the wonderful <a href="https://qpdf.readthedocs.io/">qpdf</a> has been helping me do. This ranges from merging multiple PDF files to storing decrypted versions of annoying PDFs sent by some banks.</p>
<p>I used to use <a href="https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/">pdftk</a> till some time back, but it had a lot of dependencies which were a pain to install. I exclusively use qpdf now instead.</p>]]></description>
      <content:encoded><![CDATA[      <!-- markdownlint-disable-next-line -->
<figure class="alignright"><a href="https://qpdf.readthedocs.io/"><img src="/images/2023/05/qpdf.svg"
    alt="qpdf docs"></a>
</figure>

<p>I wanted to quickly jot down some of the PDF tasks that the wonderful <a href="https://qpdf.readthedocs.io/">qpdf</a> has been helping me do. This ranges from merging multiple PDF files to storing decrypted versions of annoying PDFs sent by some banks.</p>
<p>I used to use <a href="https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/">pdftk</a> till some time back, but it had a lot of dependencies which were a pain to install. I exclusively use qpdf now instead.</p>
<h2 id="concatenating-pdfs">Concatenating PDFs</h2>
<p>Normally qpdf expects an input file for all operations. But while concatenating PDFs, you would prefer a blank slate
to which you will be adding the files. The trick is to use <code>--empty</code> as input, and then add the pages.</p>



  <blockquote>
    <p><code>--empty</code></p>
<p>This option may be given in place of infile. This causes qpdf to use a dummy input file that contains zero pages.
This option is useful in conjunction with <code>--pages</code>.</p>

  </blockquote>

<p>This following simple syntax to add the pages from other PDF documents to the input file <a href="https://github.com/qpdf/qpdf/issues/11#issuecomment-170956116">was added a few years back</a>.</p>
<p>If I want to concatenate multiple PDFs into one, I use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>qpdf --empty --pages *.pdf -- out.pdf
</span></span></code></pre></div><h2 id="decrypting-pdfs">Decrypting PDFs</h2>
<p>I had mentioned this in a prior blog post <a href="/2009/01/10/removing-encryption-from-legitimate-pdf-files-on-ubuntu/" title="My last post on decrypting pdfs">a while back</a> (14 years ago! :old_man:).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>qpdf --decrypt --password<span style="color:#f92672">=</span>mypassword input.pdf output.pdf
</span></span></code></pre></div><p>I actually now use a script to wrap this, which lets me forget the specific syntax of the command &#x1f604; and also gives me various invocation options as documented at the start of the script.</p>
<script src="https://gist.github.com/sandipb/c548d6406f3b1150df8f5a06f0fd5474.js"></script> ]]></content:encoded>
    </item>
    
    <item>
      <title>Publishing helm charts on Artifacthub</title>
      <link>https://blog.sandipb.net/2021/11/02/publishing-helm-charts-on-artifacthub/</link>
      <pubDate>Tue, 02 Nov 2021 03:11:11 +0200</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2021/11/02/publishing-helm-charts-on-artifacthub/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/artifacthub.svg"
    alt="artifacthub" width="200">
</figure>

<p>As part of my journey in learning Kubernetes this year, I ventured today in pushing <a href="https://artifacthub.io/packages/helm/sandipb/cert-manager-ca-issuer">my first tiny helm chart</a> to
Artifacthub.</p>
<p>I had been using custom made charts for a couple of months now, but mostly in the CI/CD environment of my company.
Packaging a chart for public consumption needed me to learn a few more things.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/artifacthub.svg"
    alt="artifacthub" width="200">
</figure>

<p>As part of my journey in learning Kubernetes this year, I ventured today in pushing <a href="https://artifacthub.io/packages/helm/sandipb/cert-manager-ca-issuer">my first tiny helm chart</a> to
Artifacthub.</p>
<p>I had been using custom made charts for a couple of months now, but mostly in the CI/CD environment of my company.
Packaging a chart for public consumption needed me to learn a few more things.</p>
<ul>
<li>
<p>Getting my charts bundled up a <a href="https://helm.sh/docs/topics/chart_repository/">compliant helm repo format</a>. Turns out
there is <a href="https://github.com/helm/chart-releaser-action">a Github action</a> which helps in creating one using Github
pages.</p>
<p>This gives you <a href="https://sandipb.github.io/helm-charts/">a nice web page</a> where you can put a README to help people
with instructions on how to use your charts.</p>
<p>Keeping the README synced on that webpage required <a href="https://github.com/sandipb/helm-charts/blob/main/.github/workflows/sync-readme.yaml">a separate action workflow</a> which I stole from the
<a href="https://github.com/prometheus-community/helm-charts">prometheus-community chart repo</a>.</p>
</li>
<li>
<p>The next step was discoverability. I created a repository on <a href="https://artifacthub.io">Artifacthub</a> and fiddled around
with both <a href="https://artifacthub.io/docs/topics/annotations/helm/">the annotations in <code>Chart.yaml</code></a> and the <a href="https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml"><code>artifacthub-repo.yml</code></a> file till I got most of it working.
I still find the changelog annotations feature to be awful though.</p>
<p>Copying the unique id for your repo from your artifacthub control panel to <code>artifacthub-repo.yml</code> gave me the
fancy <em>Verified Publisher</em> badge on my chart, which was nice.</p>
</li>
</ul>
<p>Now the hard part - continuing to read around for best practices to write good charts for public consumption.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Disabling comments and Google Analytics</title>
      <link>https://blog.sandipb.net/2021/10/29/disabling-comments/</link>
      <pubDate>Fri, 29 Oct 2021 17:11:11 +0200</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2021/10/29/disabling-comments/</guid>
      <description><![CDATA[<p>So, after ~20 years of maintaining this blog, I decided to disable comments entirely on the posts here. And turning off
Google Analytics.</p>
<p>This was primarily driven by a recent privacy test I did on the blog, and to my utter shock, almost a dozen different
websites seem to be contacted with every page load. I generally expected Google (for the analytics) and Disqus (for the
comments) to be the websites figuring out here, so I felt absolutely betrayed by the bunch of other websites they call
under the hood.</p>]]></description>
      <content:encoded><![CDATA[      <p>So, after ~20 years of maintaining this blog, I decided to disable comments entirely on the posts here. And turning off
Google Analytics.</p>
<p>This was primarily driven by a recent privacy test I did on the blog, and to my utter shock, almost a dozen different
websites seem to be contacted with every page load. I generally expected Google (for the analytics) and Disqus (for the
comments) to be the websites figuring out here, so I felt absolutely betrayed by the bunch of other websites they call
under the hood.</p>
<p>For comments, it is not worth keeping open an avenue for my handful of visitors who <em>may</em> give me some feedback. I have
a contact page and a Twitter profile where people have contacted me in the past. That should be good enough for me, I
guess.</p>
<p>For stats, which I would still love to have, I am presently trying out <a href="https://plausible.io">Plausible</a>, to see if it
is worth paying $9 per month.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Using Mac keychain to store and retrieve Ansible vault passwords</title>
      <link>https://blog.sandipb.net/2021/09/24/using-mac-keychain-to-store-and-retrieve-ansible-vault-passwords/</link>
      <pubDate>Fri, 24 Sep 2021 17:21:00 +0200</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2021/09/24/using-mac-keychain-to-store-and-retrieve-ansible-vault-passwords/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/keychain-macos.jpg"
    alt="Mac Keychain App logo" width="200">
</figure>

<p>When using Ansible for my home lab, an initial problem was about how to keep sudo passwords for my various machines in
an practical manner (I really don&rsquo;t like the idea of password-less sudo even in my homelab).</p>
<p>The remote users used for my ansible logins to each of my machines are different, and can be managed via the inventory
file. But the sudo passwords for them are not the same, and it is pretty annoying to enter them while running ansible on
the command line.</p>
<p>I decided to find a way to make this a little less annoying.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/keychain-macos.jpg"
    alt="Mac Keychain App logo" width="200">
</figure>

<p>When using Ansible for my home lab, an initial problem was about how to keep sudo passwords for my various machines in
an practical manner (I really don&rsquo;t like the idea of password-less sudo even in my homelab).</p>
<p>The remote users used for my ansible logins to each of my machines are different, and can be managed via the inventory
file. But the sudo passwords for them are not the same, and it is pretty annoying to enter them while running ansible on
the command line.</p>
<p>I decided to find a way to make this a little less annoying.</p>
<h2 id="storing-the-encrypted-sudo-passwords">Storing the encrypted sudo passwords</h2>
<p>I first started keeping the individual sudo passwords for my hosts in an <a href="https://docs.ansible.com/ansible/latest/user_guide/vault.html">ansible-vault</a> encrypted file
<em>outside my repo</em>. Decrypted, they would look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ae81ff">$ ansible-vault view ~/.ansible/sudo_passwords.yml</span>
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">become_passwords</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">host1</span>: <span style="color:#ae81ff">host1password</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">host2</span>: <span style="color:#ae81ff">host2password</span>
</span></span></code></pre></div><h2 id="using-the-encrypted-sudo-passwords-automatically">Using the encrypted sudo passwords automatically</h2>
<p>To use these passwords in my playbooks transparently, I did two things:</p>
<ol>
<li>
<p>I put this in a <code>group_vars/all/111_setpasswords.yaml</code> file. This will now make Ansible look for sudo passwords from
this variable that is kept inside the encrypted file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span><span style="color:#f92672">ansible_become_pass</span>: <span style="color:#e6db74">&#39;{{ become_passwords[inventory_hostname] }}&#39;</span>
</span></span></code></pre></div></li>
<li>
<p>Now to make Ansible read the encrypted file right at the beginning, I put a symlink to my encrypted vault file in
<code>group_vars/all</code> directory as well.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ ln -sf ~/.ansible/sudo_passwords.yml group_vars/all/000passwords.yml
</span></span></code></pre></div></li>
</ol>
<p>By this point, you should be able to use Ansible by providing a single password (for the encrypted file) and still use
unique sudo passwords for each of your machines.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ ansible --ask-vault-pass  ubuntu -a <span style="color:#e6db74">&#34;free -m&#34;</span>
</span></span><span style="display:flex;"><span>Vault password:
</span></span><span style="display:flex;"><span>host1 | CHANGED | rc=0 &gt;&gt;
</span></span><span style="display:flex;"><span>              total        used        free      shared  buff/cache   available
</span></span><span style="display:flex;"><span>Mem:          32005        4677       22684          25        4643       27052
</span></span><span style="display:flex;"><span>Swap:             0           0           0
</span></span><span style="display:flex;"><span>host2 | CHANGED | rc=0 &gt;&gt;
</span></span><span style="display:flex;"><span>              total        used        free      shared  buff/cache   available
</span></span><span style="display:flex;"><span>Mem:          15884        1088       12706           1        2089       14619
</span></span><span style="display:flex;"><span>Swap:             0           0           0
</span></span></code></pre></div><h2 id="avoiding-entering-vault-password-everytime">Avoiding entering vault password everytime</h2>
<p>I work on my playbooks iteratively, so I run <code>ansible</code> or <code>ansible-playbook</code> frequently. I still was mildly annoyed
having to enter the vault password every time.</p>
<p>Fortunately, Ansible provides a way to bypass this. If you set an environment variable <a href="https://docs.ansible.com/ansible/latest/reference_appendices/config.html#envvar-ANSIBLE_VAULT_PASSWORD_FILE"><code>ANSIBLE_VAULT_PASSWORD_FILE</code></a>
and point it to a shell script, Ansible will execute the script and use the output as the password.</p>
<p>So I created a pretty simple script which would do the same and put it in <code>~/.ansible/vault-env</code>.</p>
<div class="aside-note" >
	<div class="aside-text" >There is nothing sensitive about the script, so you can actually put this inside the repo as well. But since I run the same playbooks from checkouts in different paths on different machines, I decided to put the script at a consistent location of each control node to make the next step easy.</div>
</div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ cat ~/.ansible/vault-env
</span></span><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash</span>
</span></span><span style="display:flex;"><span>echo $ANSIBLE_VAULT_PASSWORD
</span></span></code></pre></div><p>Now I needed a way to set the environment variable without exposing it in bash history. For this, I created a support
script which is checked into the repo.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ cat support_scripts/set_vault_password_env.sh
</span></span><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash</span>
</span></span><span style="display:flex;"><span>export ANSIBLE_VAULT_PASSWORD_FILE<span style="color:#f92672">=</span>~/.ansible/vault-env
</span></span><span style="display:flex;"><span>export no_proxy<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;*&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>read -sp <span style="color:#e6db74">&#34;Vault password:&#34;</span> ANSIBLE_VAULT_PASSWORD
</span></span><span style="display:flex;"><span>export ANSIBLE_VAULT_PASSWORD
</span></span></code></pre></div><p>Now whenever I have some Ansible work to do, I change directory to my playbook repo, source this script and I don&rsquo;t have to enter passwords again for the rest of the session:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ source support_scripts/set_vault_password_env.sh
</span></span><span style="display:flex;"><span>Vault password:
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ ansible ... 
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ ansible-playbook ...
</span></span></code></pre></div><h2 id="making-it-even-simpler-on-mac">Making it even simpler on Mac</h2>
<p>On Mac, this process can be made even simpler by storing the password in the keychain and avoiding entering it ever!</p>
<h3 id="add-password-to-keychain">Add password to keychain</h3>
<p>Add the password to keychain. You willl have to run this script only once per mac machine you work on.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ cat support_scripts/mac/keychain_set_vault_password.sh
</span></span><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>read -sp <span style="color:#e6db74">&#34;Vault password:&#34;</span> ANSIBLE_VAULT_PASSWORD
</span></span><span style="display:flex;"><span>echo
</span></span><span style="display:flex;"><span>security add-generic-password -U -a $USER -s ansible-vault -w <span style="color:#e6db74">&#34;</span>$ANSIBLE_VAULT_PASSWORD<span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><p>Run it directly (i.e. you don&rsquo;t source it).</p>
<h3 id="point-ansible_vault_password_file-to-a-mac-specific-script">Point <code>ANSIBLE_VAULT_PASSWORD_FILE</code> to a mac specific script</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ cat ~/.ansible/vault-env-mac
</span></span><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash</span>
</span></span><span style="display:flex;"><span>security find-generic-password -a $USER -s ansible-vault -w
</span></span></code></pre></div><p>And this is the file you source before your Ansible sessions.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ cat support_scripts/mac/set_vault_password_env_from_keychain.sh
</span></span><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash</span>
</span></span><span style="display:flex;"><span>export ANSIBLE_VAULT_PASSWORD_FILE<span style="color:#f92672">=</span>~/.ansible/vault-env-mac
</span></span><span style="display:flex;"><span>export no_proxy<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;*&#39;</span>
</span></span></code></pre></div><p>Now you don&rsquo;t have to remember the vault password. The keychain will keep it safe.</p>
<div class="aside-note" >
	<div class="aside-text" >This is a simple setup for homelab where all my vault encrypted files are encrypted by the same password. For production, you abviously will use <code>vault-id</code> to set a different passwords for encrypted files for different domains.</div>
</div> ]]></content:encoded>
    </item>
    
    <item>
      <title>Installing a specific version of a Homebrew Formula</title>
      <link>https://blog.sandipb.net/2021/09/02/installing-a-specific-version-of-a-homebrew-formula/</link>
      <pubDate>Thu, 02 Sep 2021 21:11:11 +0200</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2021/09/02/installing-a-specific-version-of-a-homebrew-formula/</guid>
      <description><![CDATA[<figure class="alignright"><a href="https://brew.sh"><img src="/images/homebrew-256x256.png"
    alt="Homebrew" width="128"></a>
</figure>

<p>From time to time, I have felt the need to install a specific version of a <a href="https://brew.sh">Homebrew</a> formula. Like the other day, I was
investigating whether a particular problem I was facing with <a href="https://podman.io/">Podman</a> was because of a version bump from 2.x to 3.x.</p>
<p>At some point in the past, a way around it was to find the formula file at a particular commit of the Homebrew tap, and
directly install it via <code>brew install $URL</code>.</p>
<p>That is no longer supported, for good reason. But <a href="https://docs.brew.sh/Tips-N'-Tricks#installing-previous-versions-of-formulae">the tip provided</a> is pretty terse.</p>
<p>I thought I will just jot down the steps I took to do this the recommended way.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><a href="https://brew.sh"><img src="/images/homebrew-256x256.png"
    alt="Homebrew" width="128"></a>
</figure>

<p>From time to time, I have felt the need to install a specific version of a <a href="https://brew.sh">Homebrew</a> formula. Like the other day, I was
investigating whether a particular problem I was facing with <a href="https://podman.io/">Podman</a> was because of a version bump from 2.x to 3.x.</p>
<p>At some point in the past, a way around it was to find the formula file at a particular commit of the Homebrew tap, and
directly install it via <code>brew install $URL</code>.</p>
<p>That is no longer supported, for good reason. But <a href="https://docs.brew.sh/Tips-N'-Tricks#installing-previous-versions-of-formulae">the tip provided</a> is pretty terse.</p>
<p>I thought I will just jot down the steps I took to do this the recommended way.</p>
<div class="aside-note" >
	<div class="aside-text" >Just for posterity, I have also documented my failed attempt at trying to install it from a specific commit
URL. Don&rsquo;t do that!</div>
</div>
<h2 id="finding-the-right-version-of-the-formula">Finding the right version of the formula</h2>
<p>First, let us find the last 2.x version of the formula I am interested in -<code>podman</code>.</p>
<p>Step into the right repo for the Formula.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ brew tap-info homebrew/core
</span></span><span style="display:flex;"><span>homebrew/core: 2 commands, 5786 formulae
</span></span><span style="display:flex;"><span>/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core (6,417 files, 487.8MB) From:
</span></span><span style="display:flex;"><span>https://github.com/Homebrew/homebrew-core
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cd /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core
</span></span></code></pre></div><p>I am interested in the last 2.x version. I used the following command to find the first commit which switched the formula to 3.x.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ git log -p -G url.*podman -- Formula/podman.rb|grep -e ^commit -e https://github.com/containers/podman/
</span></span><span style="display:flex;"><span>commit 8070a9ba0b21a0285890544c88f0479a922de809
</span></span><span style="display:flex;"><span>-  url &#34;https://github.com/containers/podman/archive/v3.3.0.tar.gz&#34;
</span></span><span style="display:flex;"><span>+  url &#34;https://github.com/containers/podman/archive/v3.3.1.tar.gz&#34;
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>commit 623626510d622e64044d1d656e96386cda5cc5b9
</span></span><span style="display:flex;"><span>-  url &#34;https://github.com/containers/podman/archive/v2.2.1.tar.gz&#34;
</span></span><span style="display:flex;"><span>+  url &#34;https://github.com/containers/podman/archive/v3.0.0.tar.gz&#34;
</span></span><span style="display:flex;"><span>^C
</span></span></code></pre></div><p>Ok. So <code>2.2.1</code> is the version of the formula I am interested in.</p>
<div class="aside-note" >
	<div class="aside-text" >Most homebrew formula follow the same version as upstream. If that is not the case for your formula, you
will have to use a different search string in the git command.</div>
</div>
<h2 id="installing-the-right-way">Installing the RIGHT way</h2>
<ol>
<li>
<p>Create a local tap for storing the formula.</p>
<p>I have made it formula specific (<code>local-podman</code>). But if you tend to do this often for multiple formulas, you can
probably name it more generically. To re-emphasize, it doesn&rsquo;t <em>need</em> to be named after the formula.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span># Create a local tap <span style="color:#66d9ef">for</span> storing the formula
</span></span><span style="display:flex;"><span>$ brew tap-new $USER/local-podman
</span></span></code></pre></div></li>
<li>
<p>Actually create the copy of the formula at the specific version. <code>brew</code> does all the hard work for you! Look at how
it automatically found out the last git commit of the formula when the formula version was what you wanted.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ brew extract --version<span style="color:#f92672">=</span>2.2.1 podman $USER/local-podman
</span></span><span style="display:flex;"><span>==&gt; Searching repository history
</span></span><span style="display:flex;"><span>==&gt; Writing formula for podman from revision e2c833d to:
</span></span><span style="display:flex;"><span>/usr/local/Homebrew/Library/Taps/sandipb/homebrew-local-podman/Formula/podman@2.2.1.rb
</span></span></code></pre></div></li>
<li>
<p>Now your formula will appear in your <code>brew search</code> to be installed the usual way.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ brew search /podman/
</span></span><span style="display:flex;"><span>==&gt; Formulae
</span></span><span style="display:flex;"><span>podman                                    sandipb/local-podman/podman@2.2.1
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>If you meant &#34;podman&#34; specifically:
</span></span><span style="display:flex;"><span>It was migrated from homebrew/cask to homebrew/core.
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ HOMEBREW_NO_AUTO_UPDATE<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> brew install sandipb/local-podman/podman@2.2.1
</span></span></code></pre></div></li>
</ol>
<p>I had an unique case though. For some reason though, the checksum in that commit of the formula was broken.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ HOMEBREW_NO_AUTO_UPDATE<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> brew install sandipb/local-podman/podman@2.2.1
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>Error: SHA256 mismatch
</span></span><span style="display:flex;"><span>Expected: bd86b181251e2308cb52f18410fb52d89df7f130cecf0298bbf9a848fe7daf60
</span></span><span style="display:flex;"><span>  Actual: 3212bad60d945c1169b27da03959f36d92d1d8964645c701a5a82a89118e96d1
</span></span></code></pre></div><p>I found out the actual checksum from the official repository, edited the formula to update it and then it installed
properly. Perhaps the change in checksum was worth an investigation. It was too late in the night to do that. &#x1f604;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ curl -Ls https://github.com/containers/podman/archive/refs/tags/v2.2.1.tar.gz | sha256sum
</span></span><span style="display:flex;"><span>3212bad60d945c1169b27da03959f36d92d1d8964645c701a5a82a89118e96d1  -
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ brew edit podman@2.2.1
</span></span><span style="display:flex;"><span># ... changed checksum of archive
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ HOMEBREW_NO_AUTO_UPDATE<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> brew install podman@2.2.1
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>SUCCESS!
</span></span></code></pre></div><h2 id="installing-the-wrong-way">Installing the WRONG way</h2>
<p>As mentioned before, I used the following command to find the first commit which switched the formula to 3.x.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ git log -p -G url.*podman -- Formula/podman.rb|grep -e ^commit -e https://github.com/containers/podman/
</span></span><span style="display:flex;"><span>commit 8070a9ba0b21a0285890544c88f0479a922de809
</span></span><span style="display:flex;"><span>-  url &#34;https://github.com/containers/podman/archive/v3.3.0.tar.gz&#34;
</span></span><span style="display:flex;"><span>+  url &#34;https://github.com/containers/podman/archive/v3.3.1.tar.gz&#34;
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>commit 623626510d622e64044d1d656e96386cda5cc5b9
</span></span><span style="display:flex;"><span>-  url &#34;https://github.com/containers/podman/archive/v2.2.1.tar.gz&#34;
</span></span><span style="display:flex;"><span>+  url &#34;https://github.com/containers/podman/archive/v3.0.0.tar.gz&#34;
</span></span><span style="display:flex;"><span>^C
</span></span></code></pre></div><p>Find the commit previous to this which changed the formula.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ git log -n <span style="color:#ae81ff">1</span> 623626510d622e64044d1d656e96386cda5cc5b9^ -- Formula/podman.rb
</span></span><span style="display:flex;"><span>commit e2c833d326c45d9aaf4e26af6dd8b2f31564dc04
</span></span><span style="display:flex;"><span>Author: Rylan Polster &lt;rslpolster@gmail.com&gt;
</span></span><span style="display:flex;"><span>Date:   Wed Feb 3 00:13:48 2021 -0500
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>    formulae: use new bottle syntax
</span></span></code></pre></div><p>So, I found out that <code>e2c833d326c45d9aaf4e26af6dd8b2f31564dc04</code> is that last update to the formula which had the 2.x
version. Now to install this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ FORMULA_URL<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://raw.githubusercontent.com/Homebrew/homebrew-core/e2c833d326c45d9aaf4e26af6dd8b2f31564dc04/Formula/podman.rb&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ HOMEBREW_NO_AUTO_UPDATE<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>  brew install $FORMULA_URL
</span></span><span style="display:flex;"><span>Traceback (most recent call last):
</span></span><span style="display:flex;"><span>        9: from /usr/local/Homebrew/Library/Homebrew/brew.rb:129:in `&lt;main&gt;&#39;
</span></span><span style="display:flex;"><span>        8: from /usr/local/Homebrew/Library/Homebrew/cmd/install.rb:131:in `install&#39;
</span></span><span style="display:flex;"><span>        7: from /usr/local/Homebrew/Library/Homebrew/cli/parser.rb:308:in `parse&#39;
</span></span><span style="display:flex;"><span>        6: from /usr/local/Homebrew/Library/Homebrew/cli/parser.rb:629:in `formulae&#39;
</span></span><span style="display:flex;"><span>        5: from /usr/local/Homebrew/Library/Homebrew/cli/parser.rb:629:in `map&#39;
</span></span><span style="display:flex;"><span>        4: from /usr/local/Homebrew/Library/Homebrew/cli/parser.rb:633:in `block in formulae&#39;
</span></span><span style="display:flex;"><span>        3: from /usr/local/Homebrew/Library/Homebrew/formulary.rb:414:in `factory&#39;
</span></span><span style="display:flex;"><span>        2: from /usr/local/Homebrew/Library/Homebrew/formulary.rb:180:in `get_formula&#39;
</span></span><span style="display:flex;"><span>        1: from /usr/local/Homebrew/Library/Homebrew/formulary.rb:185:in `klass&#39;
</span></span><span style="display:flex;"><span>/usr/local/Homebrew/Library/Homebrew/formulary.rb:277:in `load_file&#39;: Invalid usage: Installation of podman from a GitHub commit URL is unsupported! `brew extract podman` to a stable tap on GitHub instead. (UsageError)
</span></span></code></pre></div><p>Wtf! Frantic web search ensues.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Using Podman as a Docker Desktop alternative using Vagrant</title>
      <link>https://blog.sandipb.net/2021/09/02/using-podman-as-a-docker-desktop-alternative-using-vagrant/</link>
      <pubDate>Thu, 02 Sep 2021 21:11:11 +0200</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2021/09/02/using-podman-as-a-docker-desktop-alternative-using-vagrant/</guid>
      <description><![CDATA[<figure class="alignright"><a href="https://podman.io"><img src="/images/podman.svg"
    alt="Podman"></a>
</figure>

<p>So the tech world went a little crazy today, as usual ignoring more consequential problems happening in the world.
<a href="https://www.docker.com/blog/updating-product-subscriptions/">Docker posted a notice</a> saying that its Mac and Windows desktop clients will no longer be free for anybody
other than individuals and small companies.</p>
<p>Here is <a href="https://www.theregister.com/2021/08/31/docker_desktop_no_longer_free/">The Register</a>:</p>



  <blockquote>
    <p>Docker will restrict use of the free version of its Docker Desktop utility to individuals or small businesses, and has
introduced a new more expensive subscription, as it searches for a sustainable business model.</p>
<p>The company has renamed its Free plan to &ldquo;Personal&rdquo; and now requires that businesses with 250 or more employees, or
higher than $10m in annual revenue, must use a paid subscription if they require Docker Desktop.</p>

  </blockquote>

<p>This gave me an excuse to try out something which I had been meaning to do for a while - use <a href="https://podman.io">Podman</a> as a replacement
for Docker desktop on my Mac.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><a href="https://podman.io"><img src="/images/podman.svg"
    alt="Podman"></a>
</figure>

<p>So the tech world went a little crazy today, as usual ignoring more consequential problems happening in the world.
<a href="https://www.docker.com/blog/updating-product-subscriptions/">Docker posted a notice</a> saying that its Mac and Windows desktop clients will no longer be free for anybody
other than individuals and small companies.</p>
<p>Here is <a href="https://www.theregister.com/2021/08/31/docker_desktop_no_longer_free/">The Register</a>:</p>



  <blockquote>
    <p>Docker will restrict use of the free version of its Docker Desktop utility to individuals or small businesses, and has
introduced a new more expensive subscription, as it searches for a sustainable business model.</p>
<p>The company has renamed its Free plan to &ldquo;Personal&rdquo; and now requires that businesses with 250 or more employees, or
higher than $10m in annual revenue, must use a paid subscription if they require Docker Desktop.</p>

  </blockquote>

<p>This gave me an excuse to try out something which I had been meaning to do for a while - use <a href="https://podman.io">Podman</a> as a replacement
for Docker desktop on my Mac.</p>
<p>I found <a href="https://www.redhat.com/sysadmin/replace-docker-podman-macos">an article</a> by <a href="https://twitter.com/davemeurer">Dave Meurer</a> to replace <a href="https://www.docker.com">Docker</a> with <a href="https://podman.io">Podman</a> on the desktop. It uses <a href="https://www.vagrantup.com/">Vagrant</a>
(with <a href="https://www.virtualbox.org/">Virtualbox</a> as the provider) to easily create a VM, and then install Podman on it, with a few scripts taking care
of some niggles of operating Podman as a non-root user.</p>
<p>The steps were pretty
detailed and complete, but there were couple of enhancements that I wanted to make:</p>
<ol>
<li>I definitely wanted to convert the raw shell script provisioning to <a href="https://docs.ansible.com/ansible/latest/">Ansible</a>, my favorite provisioning tool. It just
gives me more confidence in iteratively develop the right steps to provision the image.</li>
<li>I wanted to use private docker image repositories. Like most corporations, our Docker images are stored in a private
(often Artifactory) image registry.</li>
</ol>
<p>After a few hours of reading a bunch of man pages of <code>containers.conf</code>, <code>registries.conf</code>, etc. again and again, I finally found my sweet spot.</p>
<p>The code is here: <a href="https://github.com/sandipb/vagrant-setups/tree/main/podman">https://github.com/sandipb/vagrant-setups/tree/main/podman</a></p>
<p>Just check it out and follow the instructions, and within a few minutes, you will be able to alias <code>docker</code> to <code>podman</code>
and have a Docker Desktop-less alternative, in case you need it.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Notes on the httprouter Golang library</title>
      <link>https://blog.sandipb.net/2020/08/05/notes-on-the-httprouter-golang-library/</link>
      <pubDate>Wed, 05 Aug 2020 02:42:11 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2020/08/05/notes-on-the-httprouter-golang-library/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/BLUE_GOPHER_300.png">
</figure>

<p>For most simple to moderately complex web servers in Golang, I have always
preferred to use the standard <a href="https://golang.org/pkg/net/http/">net/http</a> library, laboring through the
parsing of RequestPaths and query parameters when necessary.</p>
<p>But when the boilerplate code for request parsing starts obscuring the business
logic, I tend to look out at other libraries. I however have always hesitated
using too heavy a framework to keep dependencies simple.</p>
<p>For HTTP routing, for example, I have found <a href="https://github.com/julienschmidt/httprouter">httprouter</a> to satisfy most
of the gaps in the standard library that I have frequently faced, and at the
same time the library is totally dep free!</p>
<p>Here are some really quick notes about using the library, mostly for my own
reference. &#x1f604;</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/BLUE_GOPHER_300.png">
</figure>

<p>For most simple to moderately complex web servers in Golang, I have always
preferred to use the standard <a href="https://golang.org/pkg/net/http/">net/http</a> library, laboring through the
parsing of RequestPaths and query parameters when necessary.</p>
<p>But when the boilerplate code for request parsing starts obscuring the business
logic, I tend to look out at other libraries. I however have always hesitated
using too heavy a framework to keep dependencies simple.</p>
<p>For HTTP routing, for example, I have found <a href="https://github.com/julienschmidt/httprouter">httprouter</a> to satisfy most
of the gaps in the standard library that I have frequently faced, and at the
same time the library is totally dep free!</p>
<p>Here are some really quick notes about using the library, mostly for my own
reference. &#x1f604;</p>
<p><strong>CONTENTS:</strong></p>
<!-- TOC -->
<ul>
<li><a href="#overview">Overview</a></li>
<li><a href="#example-usage">Example Usage</a></li>
<li><a href="#additional-features">Additional features</a></li>
</ul>
<!-- /TOC -->
<h3 id="overview">Overview</h3>
<ul>
<li>Github: <a href="https://github.com/julienschmidt/httprouter">https://github.com/julienschmidt/httprouter</a></li>
<li>Godoc: <a href="https://godoc.org/github.com/julienschmidt/httprouter">https://godoc.org/github.com/julienschmidt/httprouter</a></li>
</ul>
<p>This is a very minimalistic http router library which addresses the most
frequently felt gaps in the standard library:</p>
<ol>
<li>http method specific handlers.</li>
<li>Named parameters in the path.</li>
</ol>
<p>The feature set is deliberately kept simple, and for users who want more, the
author encourages them to use <a href="https://github.com/julienschmidt/httprouter#web-frameworks-based-on-httprouter">one of the many other libraries</a> that have
popped up wrapping this routing library.</p>
<h3 id="example-usage">Example Usage</h3>
<script src="https://gist.github.com/sandipb/7428df65a89b824b54420ec4e1e67dae.js?file=httprouter_example.go"></script>

<p><strong>Notes:</strong></p>
<ol>
<li>
<p>A route registration call specifies the HTTP method - <code>router.GET()</code>.</p>
</li>
<li>
<p>The <code>Index()</code> and <code>Hello</code> handlers are not stdlib compatible and have an
extra library specific parameter.</p>
</li>
<li>
<p>The <code>Hello()</code> handler uses a named parameter retrieved inside the handler
using the new third parameter in the handler.</p>
</li>
<li>
<p>Calls to <code>/hello/xxx/yyy</code> will not be matched to <code>/hello/:name</code> - the routing
is explicit.</p>
</li>
<li>
<p>You cannot add a <em>static</em> route overlapping the named parameter. e.g. You
cannot now have a route for <code>/hello/somewhere</code>. That second path component is
already taken by the route for <code>/hello/:name</code>.</p>
</li>
<li>
<p>As shown in the <code>Welcome()</code> handler, you can use a wildcard named parameter
(e.g. <code>*where</code> here) at the end of a routing pattern using a <code>*</code> prefix to
slurp up everything to the right of the URI. You can use a named parameter as
many times as you want as well, but the wildcard one has to be at the end of
the pattern - see (C).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ curl -s localhost:8888/welcome/sandip/to/wonderland/for/ever
</span></span><span style="display:flex;"><span>Welcome, sandip to to/wonderland/for/ever!
</span></span></code></pre></div></li>
<li>
<p>You can use a standard http handler too - see <code>Salut()</code>. To reach for the
named parameters, you need to use <code>httprouter.ParamsFromContext()</code> helper
function to retrieve the usual third parameter in the custom handlers, the
<code>Params</code> from the request context - see (A).</p>
<p>However, note that the routing function is now a non-standard
<code>router.HandlerFunc()</code> which expects the HTTP method name as the first
parameter - see (B).</p>
</li>
<li>
<p>You can provide an endpoint for static file serving using <code>ServeFiles()</code> -
see (D).</p>
<p><strong>!!Note!!</strong> however that directory index display is enabled, so an
empty file name can display directory contents.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ curl -s 127.0.0.1:8888/files/
</span></span><span style="display:flex;"><span>&lt;pre&gt;
</span></span><span style="display:flex;"><span>&lt;a href=&#34;go.mod&#34;&gt;go.mod&lt;/a&gt;
</span></span><span style="display:flex;"><span>&lt;a href=&#34;go.sum&#34;&gt;go.sum&lt;/a&gt;
</span></span><span style="display:flex;"><span>&lt;a href=&#34;simple.go&#34;&gt;simple.go&lt;/a&gt;
</span></span><span style="display:flex;"><span>&lt;/pre&gt;
</span></span></code></pre></div></li>
</ol>
<h3 id="additional-features">Additional features</h3>
<p>The router has some optional fields for provide default handling of specific
exceptions:</p>
<ol>
<li><code>router.Notfound</code> can be set to handle requests to paths which are not
matched by any routing patterns.</li>
<li>Setting <code>router.HandleMethodNotAllowed</code> to <code>true</code> allows you to set a handler
in <code>router.MethodNotAllowed</code> to handle situations when a request has been
sent to a router pattern which exists but with a different http verb. e.g.
Only a <code>GET</code> route exists but a <code>POST</code> verb was used in the request.</li>
<li>You can recover from panics in your server handlers by setting
<code>router.PanicHandler</code> to an appropriate handler which can send a suitable 5xx
error.</li>
<li>You can set CORS headers by setting <code>router.GlobalOPTIONS</code> to a suitable
handler. The Godoc provides a code sample.</li>
</ol>
<hr>
<p><small>Credit: &ldquo;Blue Gopher&rdquo; icon by <a href="https://github.com/ashleymcnamara/gophers/">Ashley McNamara</a></small></p>
<hr> ]]></content:encoded>
    </item>
    
    <item>
      <title>Automating MySQL GTID Migration With Ansible</title>
      <link>https://blog.sandipb.net/2020/07/30/automating-mysql-gtid-migration-with-ansible/</link>
      <pubDate>Thu, 30 Jul 2020 02:44:18 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2020/07/30/automating-mysql-gtid-migration-with-ansible/</guid>
      <description><![CDATA[<p>MySQL 5.6 onwards <a href="https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html">introduced GTID based replication</a> drastically simplifying
replication setup and increasing reliability. New MySQL cluster 5.6+ setups are
already done with GTID enabled by default.</p>
<p>But if you are one of those people who migrated from a pre-5.6 MySQL version,
you probably avoided enabling GTID to make the upgrade easier. In such a
situation, typically, you would upgrade the slaves to the new MySQL version, and
then failover the master to an upgraded slave. This would let you do an online
upgrade of a MySQL cluster from a pre-5.6 to a 5.6+ installation, but you will
not be able to do this with GTID enabled.</p>
<p>I recently had a requirement to move a bunch of such MySQL 5.7 clusters using
the old binlog position based replication to a GTID based replication setup.</p>
<p>Now there is <a href="https://dev.mysql.com/doc/refman/5.7/en/replication-mode-change-online-enable-gtids.html">a pretty good official document</a> about how to do
this manually, which you should definitely read up before doing this. But it is
a pain to do this manually on a bunch of servers.</p>
<p>I captured all the steps mentioned in the document into an Ansible playbook to
automate the whole process. It also includes a procedure missing in the official
document, to actually flip the ongoing replication to GTID protocol.</p>
<p>The playbook source is <a href="https://github.com/sandipb/ansible-playbooks/blob/master/mysql-gtid-migration/gtid_migrate.yml">here</a> with step by step documentation <a href="https://github.com/sandipb/ansible-playbooks/blob/master/mysql-gtid-migration">here</a>.</p>]]></description>
      <content:encoded><![CDATA[      <p>MySQL 5.6 onwards <a href="https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html">introduced GTID based replication</a> drastically simplifying
replication setup and increasing reliability. New MySQL cluster 5.6+ setups are
already done with GTID enabled by default.</p>
<p>But if you are one of those people who migrated from a pre-5.6 MySQL version,
you probably avoided enabling GTID to make the upgrade easier. In such a
situation, typically, you would upgrade the slaves to the new MySQL version, and
then failover the master to an upgraded slave. This would let you do an online
upgrade of a MySQL cluster from a pre-5.6 to a 5.6+ installation, but you will
not be able to do this with GTID enabled.</p>
<p>I recently had a requirement to move a bunch of such MySQL 5.7 clusters using
the old binlog position based replication to a GTID based replication setup.</p>
<p>Now there is <a href="https://dev.mysql.com/doc/refman/5.7/en/replication-mode-change-online-enable-gtids.html">a pretty good official document</a> about how to do
this manually, which you should definitely read up before doing this. But it is
a pain to do this manually on a bunch of servers.</p>
<p>I captured all the steps mentioned in the document into an Ansible playbook to
automate the whole process. It also includes a procedure missing in the official
document, to actually flip the ongoing replication to GTID protocol.</p>
<p>The playbook source is <a href="https://github.com/sandipb/ansible-playbooks/blob/master/mysql-gtid-migration/gtid_migrate.yml">here</a> with step by step documentation <a href="https://github.com/sandipb/ansible-playbooks/blob/master/mysql-gtid-migration">here</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Creating a Certificate Authority in 2020 for Your Soho</title>
      <link>https://blog.sandipb.net/2020/06/20/creating-a-certificate-authority-in-2020-for-your-soho/</link>
      <pubDate>Sat, 20 Jun 2020 21:38:49 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2020/06/20/creating-a-certificate-authority-in-2020-for-your-soho/</guid>
      <description><![CDATA[<p>I have a couple of systems at home which provide web services, like my Intel NUC
and my Synology NAS, and I have been wanting for a while to move all of them to
a proper https only environment.</p>
<p>But my biggest hurdle for doing that, has been the enormous pain in managing
certificates in a way that makes everybody - the servers, the browsers, the
local http clients happy. From my previous attempts, there was always the
browsers which annoyed me to no end, and I ended up getting by using improperly
made self-signed certificates and accepting all the invalid certificate warnings
that my browser threw up.</p>
<p>So this Friday night, I spent my late night hours trying to get at the bottom of
it all, and several frustrating hours later, finally made everybody happy.</p>]]></description>
      <content:encoded><![CDATA[      <p>I have a couple of systems at home which provide web services, like my Intel NUC
and my Synology NAS, and I have been wanting for a while to move all of them to
a proper https only environment.</p>
<p>But my biggest hurdle for doing that, has been the enormous pain in managing
certificates in a way that makes everybody - the servers, the browsers, the
local http clients happy. From my previous attempts, there was always the
browsers which annoyed me to no end, and I ended up getting by using improperly
made self-signed certificates and accepting all the invalid certificate warnings
that my browser threw up.</p>
<p>So this Friday night, I spent my late night hours trying to get at the bottom of
it all, and several frustrating hours later, finally made everybody happy.</p>
<p>Now, remember how I mentioned 2020 in my post title? The reason is that the
world of PKI infrastructure is not static, and is changing way faster than
people realize.</p>
<p>At this point, most SOHO people have probably stopped paying for certificates
and are using the wonderful automated certificate system provided for free by
<a href="https://letsencrypt.org/">Lets Encrypt</a>. But while that utopia is astonishingly
real, it mostly exists outside in the real Internet world. Behind the firewall,
where your private services lie, the verification systems of Lets Encrypt can&rsquo;t
reach easily. Besides, you would probably not need to use a real-world domain
inside your firewall, with entries most likely to be using private IP addresses,
which Letsencrypt cannot really verify.</p>
<p>So the only option is to run your own certificate chain inside your walls. That
means setting up a root CA, and an intermediate CA (optional but recommended)
etc.</p>
<h2 id="the-best-guide-that-i-found">The best guide that I found</h2>
<p>I read a lot of different guides over time, and even this weekend, but the one
which seemed the most useful was <a href="https://jamielinux.com/docs/openssl-certificate-authority/">OpenSSL Certificate
Authority</a> by <a href="https://jamielinux.com/">Jamie
Nguyen</a>. The guide provides step by step instructions
for creating the root CA certs, the intermediate CA certs, and finally the
server certs that you needed in the first place. It even has a section on
the revocation of certificates using CRL and OCSP.</p>
<p>While the generation of root and intermediate certificates themselves were
pretty much as described, there were several problems with the final
certificates which were generated - they were rejected by all three browsers I
was testing on - Chrome, Firefox, and Safari. And this was after I had added the
root CA certs to the Mac Keychain and Firefox certificate store.</p>
<p>And this is where the 2020 part of my post comes in. Jamie&rsquo;s guide from 2015 has
been succeeded by a bunch of changes in recent years.</p>
<h2 id="the-common-name-field-in-an-ssl-certificate-is-no-longer-supported">The &ldquo;Common Name&rdquo; field in an SSL certificate is no longer supported</h2>
<p>Chrome 58 and later specifically started enforcing this for the last couple of
years. As this <a href="https://www.thesslstore.com/blog/security-changes-in-chrome-58/">article from Hashedout
blog</a> explains:</p>



  <blockquote>
    <p>Many people don’t know that the “Common Name” field of an SSL certificate,
which contains the domain name the certificate is valid for, was actually
phased-out via RFC nearly two decades ago. Instead, the SAN (Subject
Alternative Name) field is the proper place to list the domain(s).</p>
<p>However, this has been ignored and for many years the Common Name field was
exclusively used. Chrome is finally fed up with the field that refuses to die.
In Chrome 58, the Common Name field is now ignored entirely.</p>
<p>This means certificates that were exclusively using that field to indicate the
valid domain name are no longer supported. Publicly-trusted SSL certificates
have been supporting both fields for years, ensuring maximum compatibility
with all software – so you have nothing to worry about if your certificate
came from a trusted CA.</p>
<p>This change will only affect private PKIs and other software that have not
been following spec. If you notice any sites returning the error
<code>NET::ERR_CERT_COMMON_NAME_INVALID</code>, it’s likely due to the certificate not
using SANs properly.</p>

  </blockquote>

<p><a href="https://unmitigatedrisk.com/?p=381">This additional blog post</a> mentioned in the
article has some good historical background on the topic.</p>
<h2 id="starting-september-1-2020-major-browsers-will-not-support-ssltls-certificates-with-validity-longer-than-398-days">Starting September 1 2020, major browsers will not support SSL/TLS certificates with validity longer than 398 days</h2>
<p>At first, <a href="https://www.thesslstore.com/blog/ssl-certificate-validity-will-be-limited-to-one-year-by-apples-safari-browser/">Apple</a>, and then <a href="https://www.thesslstore.com/blog/google-chrome-to-join-apple-safari-in-one-year-certificate-validity/">Google Chrome team</a> have announced
that they would only support certificates with 1 year validity (+~30 days grace
period). That is <a href="https://www.w3counter.com/globalstats.php">75% of the browser market</a>.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Chrome joins Apple in limiting public TLS certificates to 398 days starting Sept 1st.</p>&mdash; Dean Coclin (@chosensecurity) <a href="https://twitter.com/chosensecurity/status/1270819404452937729?ref_src=twsrc%5Etfw">June 10, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<p>Just to clarify, <em>it seems only leaf certificates are impacted here</em> - I created
intermediate certificates with 5 years validity (the root one was 20) and didn&rsquo;t
see a problem yet. I will find out more after September 1st, I guess.</p>
<h2 id="working-around-these-new-certificate-constraints">Working around these new certificate constraints</h2>
<p>So I had two things to fix - reduce my leaf certificate validity to 1 year, and
add <code>subjectAltName</code> field to the certificates.</p>
<p>The first part was easy. Creating leaf certificates is a hassle when you have to
create multiple ones. So I had, in any case, created a script to make it
easier. I just had to reduce the validity period there.</p>
<p>But the second one was a puzzle. I tried a bunch of hacks all around the
Internet to add the field in the Certificate Signing Request (CSR), but no
matter what I do, it won&rsquo;t appear in the final certificate. So I read around a
bit more.</p>
<p>For one, this is a bad practice, as <a href="https://security.stackexchange.com/a/14349/236970">this Stackoverflow comment</a> mentions.
CAs will not copy all attributes to the final cert for pretty reasonable
security reasons.</p>
<p>Secondly, it appears that openssl itself has two behaviors regarding copying
this attributes. The default behavior is not to copy anything in the <code>ca</code>
subcommand. If you do want this, adding the parameter <code>-copy_extensions</code> will
copy <em>everything</em>.</p>
<p>So the consensus was to add the fields explicitly in the <code>ca</code> command, and, from
the point of correctness, but not usefulness, add it to the CSR as well.</p>
<p>Adding the field itself to the commands is much of a chore - you have to add
sections to the configs dynamically (in case you want to avoid creating a config
file for every certificate you create), and all the hacks seemed tedious. I
finally found what I felt was the most elegant option - using environmental
variables. (<a href="https://security.stackexchange.com/a/86999/236970">SO comment</a>)</p>
<p>So in the <code>[server_cert]</code> section in the <code>openssl.cnf</code> config file, I added this
line:</p>
<pre tabindex="0"><code>subjectAltName=${ENV::SAN}
</code></pre><p>And in the <code>[req]</code> section, I added a line <code>req_extensions = san_env</code> and
created a section at the bottom of the config with this:</p>
<pre tabindex="0"><code>[ san_env ]
subjectAltName=${ENV::SAN}
</code></pre><p>Now all I needed to do was to make sure in the script, I added this environment
variable before invoking the openssl commands. And now, I just need a single
invocation of my script per certificate and it will generate the certificate and
the keys for me.</p>
<p>The final certificate creation script is here <a href="https://gist.github.com/sandipb/38ae381d442b3220ed69657a3ffe9e73">in this gist</a>.</p>
<h2 id="adding-trusted-certificates-to-the-browsers">Adding trusted certificates to the browsers</h2>
<p>The best way to add the trusted certificates to all my home devices was to just
add the root CA certificate in all the right places. For the Mac, this meant
adding it to the system keychain - both Safari and Chrome use this location. For
Firefox, the way to add it seemed to be in its preferences. Notice, that I am
not adding the intermediate certificate here. That is because I am sending it
from the servers (next section).</p>
<h2 id="adding-the-certificate-to-nginx">Adding the certificate to nginx</h2>
<p>There are plenty of documentation on how to do this. The important thing to
remember here is that if you have only added the root CA certificate in the
browser (which you should, and then hide the root CA private key somewhere
safe), then you need to add the intermediate certificate to the server
certificate in nginx.</p>
<p>As the <a href="https://nginx.org/en/docs/http/configuring_https_servers.html#chains">nginx doc</a> mentions:</p>



  <blockquote>
    <p>Some browsers may complain about a certificate signed by a well-known
certificate authority, while other browsers may accept the certificate
without issues. This occurs because the issuing authority has signed the
server certificate using an intermediate certificate that is not present in
the certificate base of well-known trusted certificate authorities which is
distributed with a particular browser. In this case the authority provides a
bundle of chained certificates which should be concatenated to the signed
server certificate. <em>The server certificate must appear before the chained
certificates in the combined file</em>:</p>
<p><code>$ cat www.example.com.crt bundle.crt &gt; www.example.com.chained.crt</code></p>
<p>The resulting file should be used in the <code>ssl_certificate</code> directive.</p>

  </blockquote> ]]></content:encoded>
    </item>
    
    <item>
      <title>Open Every Link in a Web Page In a New Tab</title>
      <link>https://blog.sandipb.net/2019/12/15/open-every-link-in-a-web-page-in-a-new-tab/</link>
      <pubDate>Sun, 15 Dec 2019 01:33:08 -0800</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2019/12/15/open-every-link-in-a-web-page-in-a-new-tab/</guid>
      <description><![CDATA[<p>Found this nifty trick in a <a href="https://stackoverflow.com/a/24428525/390514">github comment</a>.</p>
<p>If you add this bit of code in the <code>&lt;head&gt;</code> section of an HTML 5 page, clicking on
every link will open in a new tab.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">base</span> <span style="color:#a6e22e">target</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;_blank&#34;</span>&gt;
</span></span></code></pre></div><p><a href="https://www.w3schools.com/tags/tag_base.asp">This tag is mostly used </a> to set a URL to resolve all relative links on a page. But it offers an additional
attribute <code>target</code> to set a default target policy for links.</p>
<p>You can, of course, override the <code>target</code> attribute on a link to link basis, if you are using this feature.</p>]]></description>
      <content:encoded><![CDATA[      <p>Found this nifty trick in a <a href="https://stackoverflow.com/a/24428525/390514">github comment</a>.</p>
<p>If you add this bit of code in the <code>&lt;head&gt;</code> section of an HTML 5 page, clicking on
every link will open in a new tab.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">base</span> <span style="color:#a6e22e">target</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;_blank&#34;</span>&gt;
</span></span></code></pre></div><p><a href="https://www.w3schools.com/tags/tag_base.asp">This tag is mostly used </a> to set a URL to resolve all relative links on a page. But it offers an additional
attribute <code>target</code> to set a default target policy for links.</p>
<p>You can, of course, override the <code>target</code> attribute on a link to link basis, if you are using this feature.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Ansible privilege escalation with expect when you don&#39;t have root shell privileges</title>
      <link>https://blog.sandipb.net/2019/06/14/ansible-privilege-escalation-with-expect-when-you-dont-have-root-shell-privileges/</link>
      <pubDate>Fri, 14 Jun 2019 15:25:22 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2019/06/14/ansible-privilege-escalation-with-expect-when-you-dont-have-root-shell-privileges/</guid>
      <description><![CDATA[<p>The default Ansible privilege escalation mechanism requires broad sudo
privileges. If your production environment gives you sudo access but bars you
from getting a root shell, you are out of luck. As, the doc says - <a href="https://docs.ansible.com/ansible/latest/user_guide/become.html#can-t-limit-escalation-to-certain-commands">you cannot
expect Ansible to work when sudo commands are restricted</a>.</p>



  <blockquote>
    <p>Privilege escalation permissions have to be general. Ansible does not always
use a specific command to do something but runs modules (code) from a
temporary file name which changes every time. If you have <code>/sbin/service</code> or
<code>/bin/chmod</code> as the allowed commands this will fail with ansible as those
paths won’t match with the temporary file that ansible creates to run the
module.</p>

  </blockquote>]]></description>
      <content:encoded><![CDATA[      <p>The default Ansible privilege escalation mechanism requires broad sudo
privileges. If your production environment gives you sudo access but bars you
from getting a root shell, you are out of luck. As, the doc says - <a href="https://docs.ansible.com/ansible/latest/user_guide/become.html#can-t-limit-escalation-to-certain-commands">you cannot
expect Ansible to work when sudo commands are restricted</a>.</p>



  <blockquote>
    <p>Privilege escalation permissions have to be general. Ansible does not always
use a specific command to do something but runs modules (code) from a
temporary file name which changes every time. If you have <code>/sbin/service</code> or
<code>/bin/chmod</code> as the allowed commands this will fail with ansible as those
paths won’t match with the temporary file that ansible creates to run the
module.</p>

  </blockquote>

<p>Our company, for good reasons, has these restrictions. Root shells bypass audit
logs and can let really bad things happen by mistake. Even if I am working on
my personal hosts, I try to make it a habit of not working in a root shell. The
convenience is just not worth the risk.</p>
<p>So, anyway, ansible is pretty useless for anything involving root user on our systems.</p>
<p>However, if we look at the actual problem in more detail, the issue is clearly the way sudo is invoked.</p>
<p>Without <code>become</code> ansible essentially runs the following on the remote host:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>sh -c &#34;/usr/bin/python /path/to/ansible/compiled/python/script&#34;
</span></span></code></pre></div><p>With <code>become</code> however, ansible runs something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>sh -c &#34;sudo -u root /bin/sh -c &#39;echo BECOME-SUCCESS-oqgdgdpwngxeakkmhtvcxzvhnwtsxzzm ; /usr/bin/python  /path/to/ansible/compiled/python/script&#34;
</span></span></code></pre></div><p>It is that <code>echo</code> statement, which is possibly being used by ansible to
distinguish between a privilege escalation failure and a task failure, which is
bringing in that requirement to run a shell within sudo. Unfortunately, this is
something the ansible team has decided to stick with. So to me, one way out was
to stop relying on ansible to run sudo, and run sudo myself in an ordinary
command and figure out a way to supply a password to the inevitable prompt.</p>
<p>I tried to see if I can use that old trick of using
<a href="https://core.tcl-lang.org/expect/index"><code>expect</code></a> to supply the password.
Ansible has an <a href="https://docs.ansible.com/ansible/latest/modules/expect_module.html">expect module</a>.
Unfortunately, it requires the python library
<a href="https://pexpect.readthedocs.io/en/stable/"><code>pexpect</code></a>, which performs similar
functionality in pure python, to be <code>pexpect &gt;= 3.3</code> which is not available in
RHEL7 hosts in our production environment.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ sudo yum list pexpect
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>Available Packages
</span></span><span style="display:flex;"><span>pexpect.noarch           2.3-11.el7         release-rhel-x86_64-workstation-7-r03
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cat /etc/redhat-release
</span></span><span style="display:flex;"><span>Red Hat Enterprise Linux Workstation release 7.3 (Maipo)
</span></span></code></pre></div><p>So I was left to use the regular <code>expect</code> command, available in most server
environments for eons, to do my job. I had to read up a bit of TCL to find my
way around. That and <a href="https://stackoverflow.com/a/23632210/390514">this stackoverflow answer</a>
helped me construct this proof-of-concept playbook which does the job.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">hosts</span>: <span style="color:#ae81ff">all</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">gather_facts</span>: <span style="color:#66d9ef">no</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">vars</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">sudo_cmd</span>: <span style="color:#ae81ff">id</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">vars_prompt</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">sudo_password</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">msg</span>: <span style="color:#ae81ff">Enter sudo password</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">private</span>: <span style="color:#66d9ef">yes</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">tasks</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># </span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;run sudo&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">shell</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            set timeout 10
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            spawn sudo {{ sudo_cmd }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            expect {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">                &#34;sudo*password*: &#34; { send &#34;{{sudo_password}}\n&#34; }
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            }
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            expect eof
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            lassign [wait] pid spawnid os_error_flag ret_code
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            if {$os_error_flag != 0} {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">                exit $os_error_flag
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            }
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            exit $ret_code</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">args</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">executable</span>: <span style="color:#ae81ff">/usr/bin/expect</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">no_log</span>: <span style="color:#66d9ef">True</span>
</span></span></code></pre></div><p>This script will prompt for the sudo password once at the beginning.</p>
<p>You can then run your sudo command like this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>ansible-playbook -v  --limit somehost sudo.yml -e &#34;sudo_cmd=ls /root&#34;
</span></span></code></pre></div><p>Of course, one gotcha is that in many stock deployments, the <code>expect</code> package
is not installed and you might have to install it first.</p>
<p>The usual security risks of having secrets passed into shell commands in
ansible apply, of course. The <code>no_log</code> parameter is especially important,
without which ansible will leak your password to syslog on the remote host.</p>
<p>It is a hack, I know. If there is a better way of doing it, I would be really
interested to know. I couldn&rsquo;t find anything on ansible forums and
documentation for my particular needs.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Integer maths in Go using constants with exponential notation</title>
      <link>https://blog.sandipb.net/2018/10/05/integer-maths-in-go-using-constants-with-exponential-notation/</link>
      <pubDate>Fri, 05 Oct 2018 15:09:36 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2018/10/05/integer-maths-in-go-using-constants-with-exponential-notation/</guid>
      <description><![CDATA[<p>I seem to learn more about the nuances of the Go language every other day.
Sometime back, I had looked at <a href="https://blog.sandipb.net/2017/11/13/go-static-typing-magic/">how Go untyped constants work during maths
operations with typed variables</a>.</p>
<p>I just found another significant part of the spec that I had previously glossed
over, this one is also about <a href="https://golang.org/ref/spec#Constants">untyped constants</a> - numeric constants
in Go live in an unified space with arbitrary precision and a fungible numeric
type.</p>
<p>So, coming from a language like Python, this might not surprise us:</p>]]></description>
      <content:encoded><![CDATA[      <p>I seem to learn more about the nuances of the Go language every other day.
Sometime back, I had looked at <a href="https://blog.sandipb.net/2017/11/13/go-static-typing-magic/">how Go untyped constants work during maths
operations with typed variables</a>.</p>
<p>I just found another significant part of the spec that I had previously glossed
over, this one is also about <a href="https://golang.org/ref/spec#Constants">untyped constants</a> - numeric constants
in Go live in an unified space with arbitrary precision and a fungible numeric
type.</p>
<p>So, coming from a language like Python, this might not surprise us:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">$</span> python2
</span></span><span style="display:flex;"><span>Python <span style="color:#ae81ff">2.7.15</span> (default, Sep <span style="color:#ae81ff">18</span> <span style="color:#ae81ff">2018</span>, <span style="color:#ae81ff">20</span>:<span style="color:#ae81ff">16</span>:<span style="color:#ae81ff">18</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;&gt;</span> i <span style="color:#f92672">=</span> <span style="color:#ae81ff">2000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;&gt;</span> print i <span style="color:#f92672">/</span> <span style="color:#ae81ff">1000</span>, type(i <span style="color:#f92672">/</span> <span style="color:#ae81ff">1000</span>)
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">2</span> <span style="color:#f92672">&lt;</span>type <span style="color:#e6db74">&#39;int&#39;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&gt;&gt;&gt;</span> print i <span style="color:#f92672">/</span> <span style="color:#ae81ff">1e3</span>, type(i <span style="color:#f92672">/</span> <span style="color:#ae81ff">1e3</span>)
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">2.0</span> <span style="color:#f92672">&lt;</span>type <span style="color:#e6db74">&#39;float&#39;</span><span style="color:#f92672">&gt;</span></span></span></code></pre></div>
<p>This is because the exponential format (1e3) was a float, and forced the
division to be a float division.</p>
<div class="aside-note" >
	<div class="aside-text" >Admittedly, Python3 corrected this specific instance by making all divisions
return a double value (<a href="https://www.python.org/dev/peps/pep-0238/">PEP 238</a>),
that doesn&rsquo;t take away the fact that the numeric literal itself is treated like
a float.</div>
</div>
<p>This therefore was a surprise for me when I found out that this works in Go.</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">2000</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">2000.0</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;i: %v (%T)\n&#34;</span>, <span style="color:#a6e22e">i</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1e3</span>, <span style="color:#a6e22e">i</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1e3</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;j: %v (%T)\n&#34;</span>, <span style="color:#a6e22e">j</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1e3</span>, <span style="color:#a6e22e">j</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1e3</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Prints:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//   i: 2 (int)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//   j: 2 (float64)</span></span></span></code></pre></div>
(<a href="https://play.golang.org/p/SZ2IkpM3UtD">Play link</a>)</p>
<p>So, you can use the exponential notation for working with large numbers in Go,
something I thought was a no-no earlier.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">now</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">secs</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">now</span>.<span style="color:#a6e22e">Unix</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">millis</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">now</span>.<span style="color:#a6e22e">UnixNano</span>() <span style="color:#f92672">/</span> <span style="color:#ae81ff">1e6</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;Secs   since 1970: %v (%T)\n&#34;</span>, <span style="color:#a6e22e">secs</span>, <span style="color:#a6e22e">secs</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;Millis since 1970: %v (%T)\n&#34;</span>, <span style="color:#a6e22e">millis</span>, <span style="color:#a6e22e">millis</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Prints:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//    Secs   since 1970: 1538782570 (int64)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//    Millis since 1970: 1538782570453 (int64)</span></span></span></code></pre></div>
<p>(<a href="https://play.golang.org/p/_lrcCDNXLM0">Play link</a>, note that you will find
more useful values by running this on your own computer, because current time
is a <a href="https://blog.golang.org/playground">weird concept in the Go playground</a>)</p>
<p>The <a href="https://blog.golang.org/constants#TOC_12.">Go blog post on constants</a> has an excellent section demonstrating
this:</p>
<blockquote>
The concept of untyped constants in Go means that all the numeric constants,
whether integer, floating-point, complex, or even character values, live in a
kind of unified space. It's when we bring them to the computational world of
variables, assignments, and operations that the actual types matter. But as
long as we stay in the world of numeric constants, we can mix and match values
as we like.
<p>All these constants have numeric value 1:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1.000</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1e3</span><span style="color:#f92672">-</span><span style="color:#ae81ff">99.0</span><span style="color:#f92672">*</span><span style="color:#ae81ff">10</span><span style="color:#f92672">-</span><span style="color:#ae81ff">9</span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#39;\x01&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#39;\u0001&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#39;b&#39;</span> <span style="color:#f92672">-</span> <span style="color:#e6db74">&#39;a&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1.0</span><span style="color:#f92672">+</span><span style="color:#ae81ff">3i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">3.0i</span></span></span></code></pre></div>
<p>Therefore, although they have different implicit default types, written as
untyped constants they can be assigned to a variable of any integer type.</p>
</blockquote>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Runit, Chpst and ulimit defaults</title>
      <link>https://blog.sandipb.net/2018/09/14/runit-chpst-and-ulimit-defaults/</link>
      <pubDate>Fri, 14 Sep 2018 14:03:18 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2018/09/14/runit-chpst-and-ulimit-defaults/</guid>
      <description><![CDATA[<p>So I ran into this problem at work today with an runit based service breaching open files limit.</p>
<p>My first thought was to increase the system ulimit for <code>nofile</code> in <code>/etc/security/limits.conf</code>. I changed this
from 30k to about 60k. But strangely, the service still keep dying.</p>]]></description>
      <content:encoded><![CDATA[      <p>So I ran into this problem at work today with an runit based service breaching open files limit.</p>
<p>My first thought was to increase the system ulimit for <code>nofile</code> in <code>/etc/security/limits.conf</code>. I changed this
from 30k to about 60k. But strangely, the service still keep dying.</p>
<p>Since the service uses <a href="http://smarden.org/runit/chpst.8.html"><code>chpst</code></a>, my first suspect was that <code>chpst</code>
was trying to be overbearing and changing the system limits before starting up the process. The service I was
running was Python based, and so I added this small snippet in the beginning of the program to see what limits
was it seeing.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> resource
</span></span><span style="display:flex;"><span>logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;rlimit for nofile: </span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#34;</span>, resource<span style="color:#f92672">.</span>getrlimit(resource<span style="color:#f92672">.</span>RLIMIT_NOFILE))
</span></span></code></pre></div><p>The logs showed:</p>
<p><code>[INFO] rlimit for nofile: (1024, 4096)</code></p>
<p>That was shocking. None of those values are anywhere configured in the system.
<code>lsof</code> of the process had earlier showed me that the process had about a
thousand open files before it crashed, so that explained why it was crashing -
it was breaching the soft limit.</p>
<p>I looked at the documentation for <code>chpst</code> and found a option <code>-o</code> which changes
the open files limit. So I set that in the chpst invocation (<code>exec chpst -o 60000 ...</code>), and I got:</p>
<p><code>[INFO] rlimit for nofile: (4096, 4096)</code></p>
<p>It seems that the <code>-o</code> only affects the soft limit. I took the win and the
service recovered, but after the crisis passed, I kept digging. I was curious
where all these limits were coming from.</p>
<aside class="aside-tip" >
<pre><code>&lt;div class=&quot;aside-text&quot;&gt;
&lt;h2 id=&quot;finding-soft-and-hard-limits&quot;&gt;
    Finding soft and hard limits
&lt;/h2&gt;&lt;p&gt;I found there are two ways to find out the soft and hard resource limits.&lt;/p&gt;
</code></pre>
<p>One is using <code>ulimit</code> and the <code>-H</code> and <code>-S</code> switches.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span># on my hosting provider running Ubuntu
</span></span><span style="display:flex;"><span>$ <span style="color:#f92672">(</span>ulimit -Sn ;ulimit -Hn<span style="color:#f92672">)</span> | paste -sd, -
</span></span><span style="display:flex;"><span>1024,4096
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span># on my mac
</span></span><span style="display:flex;"><span>$ <span style="color:#f92672">(</span>ulimit -Sn ;ulimit -Hn<span style="color:#f92672">)</span> | paste -sd, -
</span></span><span style="display:flex;"><span>7168,unlimited
</span></span></code></pre></div><p>For Linux boxes, the other way is to access the special proc file for the
process at <code>/proc/self/limits</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ sed -n -e 1p -e /open/p &lt; /proc/self/limits
</span></span><span style="display:flex;"><span>Limit                     Soft Limit           Hard Limit           Units
</span></span><span style="display:flex;"><span>Max open files            1024                 4096                 files
</span></span></code></pre></div></div>
</aside>
<p>The <a href="https://github.com/vulk/runit/blob/master/src/chpst.c">chpst source</a>
didn&rsquo;t reveal any limits being imposed. I couldn&rsquo;t figure out any other call to
<code>setrlimit</code> in the  rest of the runit sources either.</p>
<p>On a hunch, I tried to print the limits <em>before</em> <code>chpst</code> is called using this
<code>run</code> file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>exec 2&gt;&amp;<span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Soft limits&#34;</span>
</span></span><span style="display:flex;"><span>ulimit -S -a
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Hard limits&#34;</span>
</span></span><span style="display:flex;"><span>ulimit -H -a
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>exec chpst ...
</span></span></code></pre></div><p>It got me:</p>
<ul>
<li><code>open files                      (-n) 1024 # soft limit</code></li>
<li><code>open files                      (-n) 4096 # hard limit</code></li>
</ul>
<p>The actual ulimit values for the root user were <code>60000 (soft),60000 (hard)</code>.
That showed me that this limits modification was not happening because of
<code>chpst</code>, but probably because of <code>runsv</code> or some other part of the runit
system. I could be wrong though because, like I said before, I couldn&rsquo;t find a
call to <code>setrlimit</code> in the sources.</p>
<p>Curiously, printing the ulimit values in the <code>run</code> file showed me another odd
change from the system limits - the max procs limit (<code>ulimit -u</code>). It seems
that when the run file is executing, the <em>soft limit for this setting is set to
the hard limit</em>.</p>
<p>On the RHEL6 machine I was running, the root user showed these limits in the
root shell: <code>(1024,514975)</code>.  In the run file however, the equivalents were
<code>(514975,514975)</code>. This was so weird, I double checked.</p>
<p><em>Why is the runit service being conservative with resource limits with regard to
the max open files limit, but more generous with the max procs limit?</em></p>
<p>I have no idea.</p>
<p>In the end, yes, you can workaround the default limits of runit by using ulimit
in the shell script before invoking <code>chpst</code>, but when you don&rsquo;t, I hope this
helps in reminding ourselves the limits it places on the services that it
manages.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Using Zap - Working With Global Loggers</title>
      <link>https://blog.sandipb.net/2018/05/04/using-zap-working-with-global-loggers/</link>
      <pubDate>Fri, 04 May 2018 02:10:04 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2018/05/04/using-zap-working-with-global-loggers/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/zap.png">
</figure>

<p>Sometimes instead of creating a logger and then passing it around, it is convenient to just use a
global logger.</p>
<p>The standard <code>log</code> library allows you to both create a custom logger using
<a href="https://golang.org/pkg/log/#New">log.New()</a> or directly use a <em>standard</em> logger instance by calling
the package helper functions <a href="https://golang.org/pkg/log/#Printf">log.Printf()</a> and the like.</p>
<p><code>zap</code> provides such a functionality as well using <code>zap.L()</code> and <code>zap.S()</code>, however using them
didn&rsquo;t seem so straight forward to me.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/zap.png">
</figure>

<p>Sometimes instead of creating a logger and then passing it around, it is convenient to just use a
global logger.</p>
<p>The standard <code>log</code> library allows you to both create a custom logger using
<a href="https://golang.org/pkg/log/#New">log.New()</a> or directly use a <em>standard</em> logger instance by calling
the package helper functions <a href="https://golang.org/pkg/log/#Printf">log.Printf()</a> and the like.</p>
<p><code>zap</code> provides such a functionality as well using <code>zap.L()</code> and <code>zap.S()</code>, however using them
didn&rsquo;t seem so straight forward to me.</p>
<div class="aside-note" >
	<div class="aside-text" ><p>This post is the fourth of a series of posts showing different ways of using <code>zap</code>. The other posts
are  <a href="https://blog.sandipb.net/2018/05/02/using-zap-simple-use-cases/">Simple use cases</a>, <a href="https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-loggers/">Creating custom loggers</a> and <a href="https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-encoders/">Custom encoders</a>.</p>
<p>This documentation was written for zap v1.8.</p>
</div>
</div>
<div class="pure-u-1 aside-edit" >
	<div class="pure-u-20-24 aside-text" >The full source for the code extracts in the rest of the post is
<a href="https://github.com/sandipb/zap-examples/blob/master/src/globallogger/main.go">here</a></div>
</div>
<p>You can access the global standard logger using <a href="https://godoc.org/go.uber.org/zap#L">zap.L()</a>. The function returns the shared logger instance. A <em>sugared</em> version is accessible via <a href="https://godoc.org/go.uber.org/zap#S">zap.S()</a>.</p>
<p>From what I have seen in my brief usage of these loggers, this shared logger is not useful out of the box - if you just use them right away, they provide no output. Their sole purpose seems to be to provide a simple way to retrieve this instance anywhere in your code.</p>
<p>If you really want to use this standard logger usefully, you need to replace the <em>core</em> of the logger with
that of a different logger using
<a href="https://godoc.org/go.uber.org/zap#ReplaceGlobals">zap.ReplaceGlobals()</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;\n*** Using the global logger out of the box\n\n&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">S</span>().<span style="color:#a6e22e">Infow</span>(<span style="color:#e6db74">&#34;An info message&#34;</span>, <span style="color:#e6db74">&#34;iteration&#34;</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;\n*** After replacing the global logger with a development logger\n\n&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewDevelopment</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">ReplaceGlobals</span>(<span style="color:#a6e22e">logger</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">S</span>().<span style="color:#a6e22e">Infow</span>(<span style="color:#e6db74">&#34;An info message&#34;</span>, <span style="color:#e6db74">&#34;iteration&#34;</span>, <span style="color:#ae81ff">1</span>)
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>*** Using the global logger out of the box


*** After replacing the global logger with a development logger

2018-05-02T16:24:40.992-0700    INFO    globallogger/main.go:17 An info message {&#34;iteration&#34;: 1}
</code></pre><p>There is also a way provided to <em>undo</em> a core replacement in the global loggers.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;\n*** After replacing the global logger with a development logger\n\n&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewDevelopment</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">ReplaceGlobals</span>(<span style="color:#a6e22e">logger</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">S</span>().<span style="color:#a6e22e">Infow</span>(<span style="color:#e6db74">&#34;An info message&#34;</span>, <span style="color:#e6db74">&#34;iteration&#34;</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;\n*** After replacing the global logger with a production logger\n\n&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewProduction</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">undo</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">ReplaceGlobals</span>(<span style="color:#a6e22e">logger</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">S</span>().<span style="color:#a6e22e">Infow</span>(<span style="color:#e6db74">&#34;An info message&#34;</span>, <span style="color:#e6db74">&#34;iteration&#34;</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;\n*** After undoing the last replacement of the global logger\n\n&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">undo</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">S</span>().<span style="color:#a6e22e">Infow</span>(<span style="color:#e6db74">&#34;An info message&#34;</span>, <span style="color:#e6db74">&#34;iteration&#34;</span>, <span style="color:#ae81ff">1</span>)
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>*** After replacing the global logger with a development logger

2018-05-02T16:24:40.992-0700    INFO    globallogger/main.go:17 An info message {&#34;iteration&#34;: 1}

*** After replacing the global logger with a production logger

{&#34;level&#34;:&#34;info&#34;,&#34;ts&#34;:1525303480.993161,&#34;caller&#34;:&#34;globallogger/main.go:22&#34;,&#34;msg&#34;:&#34;An info message&#34;,&#34;iteration&#34;:1}

*** After undoing the last replacement of the global logger

2018-05-02T16:24:40.993-0700    INFO    globallogger/main.go:26 An info message {&#34;iteration&#34;: 1}
</code></pre> ]]></content:encoded>
    </item>
    
    <item>
      <title>Using Zap - Creating custom encoders</title>
      <link>https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-encoders/</link>
      <pubDate>Thu, 03 May 2018 16:07:24 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-encoders/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/zap.png">
</figure>

<p>The various implementations of field encoders provided in <code>zap</code> can sometimes feel inadequate. For
example, you might want the logging output to be similar to that in syslog or other common log
formats. You might want the timestamps in the log to ignore seconds, or the log level to be wrapped
within square brackets.</p>
<p>To have your own custom formatters for the metadata fields you need to write <em>custom encoders</em>.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/zap.png">
</figure>

<p>The various implementations of field encoders provided in <code>zap</code> can sometimes feel inadequate. For
example, you might want the logging output to be similar to that in syslog or other common log
formats. You might want the timestamps in the log to ignore seconds, or the log level to be wrapped
within square brackets.</p>
<p>To have your own custom formatters for the metadata fields you need to write <em>custom encoders</em>.</p>
<hr>
<p><strong>Contents:</strong></p>
<!-- TOC depthFrom:1 orderedList:false -->
<ul>
<li><a href="#1-performance-considerations">1. Performance considerations</a></li>
<li><a href="#2-customizing-timestamp-formatting">2. Customizing timestamp formatting</a></li>
<li><a href="#3-customizing-level-formatting">3. Customizing level formatting</a></li>
</ul>
<!-- /TOC -->
<div class="aside-note" >
	<div class="aside-text" ><p>This post is third in a series of posts showing different ways of using <code>zap</code>. The other
posts are  <a href="https://blog.sandipb.net/2018/05/02/using-zap-simple-use-cases/">Simple use cases</a>, <a href="https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-loggers/">Creating custom
loggers</a> and <a href="https://blog.sandipb.net/2018/05/04/using-zap-working-with-global-loggers/">Working With Global Loggers</a></p>
<p>This documentation was written for zap v1.8.</p>
</div>
</div>
<div class="pure-u-1 aside-edit" >
	<div class="pure-u-20-24 aside-text" >The full source for the code extracts in the rest of the post is <a href="https://github.com/sandipb/zap-examples/blob/master/src/customencoder/main.go">here</a></div>
</div>
<h1 id="1-performance-considerations">1. Performance considerations</h1>
<p>You can use custom encoders for formatting time, level, caller, etc. One caveat is that you need
these encoders to be as efficient as possible so as to not negate the memory/speed advantages of zap
itself. After all, these functions are called for <em>every</em> line of log to be emitted! So avoid
creating temporary variables, or doing any expensive calculations.</p>
<p>That said, the examples below are just a demonstration. I am not claiming at all that they are
efficient replacements of the default functionality. &#x1f604;</p>
<h1 id="2-customizing-timestamp-formatting">2. Customizing timestamp formatting</h1>
<p>Here is an implementation which uses the syslog format often found in the wild.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">SyslogTimeEncoder</span>(<span style="color:#a6e22e">t</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#a6e22e">enc</span> <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">PrimitiveArrayEncoder</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">enc</span>.<span style="color:#a6e22e">AppendString</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Format</span>(<span style="color:#e6db74">&#34;Jan  2 15:04:05&#34;</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">EncoderConfig</span>.<span style="color:#a6e22e">EncodeTime</span> = <span style="color:#a6e22e">SyslogTimeEncoder</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">Build</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This should have a syslog style timestamp&#34;</span>)
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>May  2 18:54:55 INFO    This should have a syslog style timestamp
</code></pre><p>If you noticed in the implementation that the encoder is supposed to append primitives to an array
like object. zap uses this array to efficiently encode output with minimal memory allocations.</p>
<h1 id="3-customizing-level-formatting">3. Customizing level formatting</h1>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">CustomLevelEncoder</span>(<span style="color:#a6e22e">level</span> <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">Level</span>, <span style="color:#a6e22e">enc</span> <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">PrimitiveArrayEncoder</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">enc</span>.<span style="color:#a6e22e">AppendString</span>(<span style="color:#e6db74">&#34;[&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">level</span>.<span style="color:#a6e22e">CapitalString</span>() <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;]&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">EncoderConfig</span>.<span style="color:#a6e22e">EncodeLevel</span> = <span style="color:#a6e22e">CustomLevelEncoder</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">Build</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This should have a bracketed level name&#34;</span>)
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>May  2 18:54:55 [INFO]  This should have a bracketed level name
</code></pre><div class="aside-note" >
	<div class="aside-text" >I am creating a single string from multiple substrings and appending it to the array.
This might cause temporary memory allocations. I had to do this because if I appended the substrings
separately, it seemed to me that <code>zap</code> was considering them as separate fields in the output and
spacing them out with whitespace.</div>
</div>
<p>Similar customization can be done for other metadata fields as well. You can look <a href="https://sourcegraph.com/github.com/uber-go/zap/-/blob/zapcore/encoder.go">at the zap
source</a> to find the
general pattern of these implementations.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Using Zap - Creating custom loggers</title>
      <link>https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-loggers/</link>
      <pubDate>Thu, 03 May 2018 03:47:38 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-loggers/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/zap.png">
</figure>

<p>Using the logger presets in <code>zap</code> can be a huge time saver, but if you really need to tweak the
logger, you need to explore ways to create custom loggers. <code>zap</code> provides an easy way to create
custom loggers using a configuration struct. You can either create the logger configuration using a
JSON object (possibly kept in a file next to your other app config files), or you can statically
configure it using the native <code>zap.Config</code> struct, which we will explore here.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/zap.png">
</figure>

<p>Using the logger presets in <code>zap</code> can be a huge time saver, but if you really need to tweak the
logger, you need to explore ways to create custom loggers. <code>zap</code> provides an easy way to create
custom loggers using a configuration struct. You can either create the logger configuration using a
JSON object (possibly kept in a file next to your other app config files), or you can statically
configure it using the native <code>zap.Config</code> struct, which we will explore here.</p>
<hr>
<p><strong>Contents:</strong></p>
<!-- TOC depthFrom:1 orderedList:false -->
<ul>
<li><a href="#1-using-the-zap-config-struct-to-create-a-logger">1. Using the zap config struct to create a logger</a></li>
<li><a href="#2-customizing-the-encoder">2. Customizing the encoder</a></li>
<li><a href="#3-metadata-field-encoder-alternatives">3. Metadata field encoder alternatives</a></li>
<li><a href="#4-changing-logger-behavior-on-the-fly">4. Changing logger behavior on the fly</a></li>
</ul>
<!-- /TOC -->
<div class="aside-note" >
	<div class="aside-text" ><p>This post is second of a series of posts showing different ways of using <code>zap</code>. The other
posts are <a href="https://blog.sandipb.net/2018/05/02/using-zap-simple-use-cases/">Simple use cases</a>, <a href="https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-encoders/">Custom encoders</a> and and <a href="https://blog.sandipb.net/2018/05/04/using-zap-working-with-global-loggers/">Working With Global Loggers</a></p>
<p>This documentation was written for zap v1.8.</p>
</div>
</div>
<div class="pure-u-1 aside-edit" >
	<div class="pure-u-20-24 aside-text" >The full source for the code extracts in the rest of the post is <a href="https://github.com/sandipb/zap-examples/blob/master/src/customlogger/main.go">here</a></div>
</div>
<h1 id="1-using-the-zap-config-struct-to-create-a-logger">1. Using the zap config struct to create a logger</h1>
<p>Loggers can be created using a configuration struct <code>zap.Config</code>. You are expected to fill in the
struct with required values, and then call the <code>.Build()</code> method on the struct to get your logger.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// general pattern</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">cfg</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Config</span>{<span style="color:#f92672">...</span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">Build</span>()
</span></span></code></pre></div><p>There are no sane defaults for the struct. You have to, at the minimum, provide values for the three
classes of settings that zap needs.</p>
<ul>
<li>
<p><em>encoder</em>: Just adding a <code>Encoding: &quot;xxx&quot;</code> field is a minimum. Using <code>json</code>
here as the value will create a default JSON encoder. The other alternative is using <code>console</code>.
<br><br>
You can customize the encoder (which you almost certainly have to, because the defaults aren&rsquo;t
very useful), by adding a <code>zapcore.EncoderConfig</code> struct to the <code>EncoderConfig</code> field.</p>
</li>
<li>
<p><em>level enabler</em>: This is an interface type which allows <code>zap</code> to determine whether a
message at a particular level should be displayed. In the zap config struct,
you provide such a type using the <code>AtomicLevel</code> wrapper in the <code>Level</code> field.</p>
</li>
<li>
<p><em>sink</em>: This is the destination of the log messages. You can specify multiple output paths using
the <code>OutputPaths</code> field which accepts a list of path names. Output will be sent to all of these
files. Magic values like <code>&quot;stderr&quot;</code> and <code>&quot;stdout&quot;</code> can be used for the usual purposes.</p>
</li>
</ul>
<h1 id="2-customizing-the-encoder">2. Customizing the encoder</h1>
<p>Just mentioning an encoder type in the struct is not enough. By default the
JSON encoder only outputs fields specifically provided in the log messages.</p>
<p>Here are the least number of struct fields which will not throw an error when you call <code>.Build()</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Encoding</span>:    <span style="color:#e6db74">&#34;json&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Level</span>:       <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewAtomicLevelAt</span>(<span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">DebugLevel</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">OutputPaths</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;stdout&#34;</span>},
</span></span><span style="display:flex;"><span>}.<span style="color:#a6e22e">Build</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message with fields&#34;</span>, <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;region&#34;</span>, <span style="color:#e6db74">&#34;us-west&#34;</span>), <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#ae81ff">2</span>))
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>{&#34;region&#34;:&#34;us-west&#34;,&#34;id&#34;:2}
</code></pre><p>Even the message is not printed!</p>
<p>To add the message in the JSON encoder, you need to specify the JSON key which will have this value
in the output.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Encoding</span>:    <span style="color:#e6db74">&#34;json&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Level</span>:       <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewAtomicLevelAt</span>(<span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">DebugLevel</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">OutputPaths</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;stdout&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">EncoderConfig</span>: <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">EncoderConfig</span>{  
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">MessageKey</span>: <span style="color:#e6db74">&#34;message&#34;</span>,  <span style="color:#75715e">// &lt;--</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>}.<span style="color:#a6e22e">Build</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message with fields&#34;</span>, <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;region&#34;</span>, <span style="color:#e6db74">&#34;us-west&#34;</span>), <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#ae81ff">2</span>))
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>{&#34;message&#34;:&#34;This is an INFO message with fields&#34;,&#34;region&#34;:&#34;us-west&#34;,&#34;id&#34;:2}
</code></pre><p><code>zap</code> can add more metadata to the message like level name, timestamp, caller, stacktrace, etc.
Unless you specifically mention the JSON key in the output corresponding to a metadata, it is
not displayed.</p>
<p>Note that these metadata field names <em>have</em> to be paired with an <em>encoder</em> else zap just burns and
dies (!!).</p>
<p>For example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cfg</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Encoding</span>:         <span style="color:#e6db74">&#34;json&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Level</span>:            <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewAtomicLevelAt</span>(<span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">DebugLevel</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">OutputPaths</span>:      []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;stderr&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ErrorOutputPaths</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;stderr&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">EncoderConfig</span>: <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">EncoderConfig</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">MessageKey</span>: <span style="color:#e6db74">&#34;message&#34;</span>,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">LevelKey</span>:    <span style="color:#e6db74">&#34;level&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">EncodeLevel</span>: <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">CapitalLevelEncoder</span>,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">TimeKey</span>:    <span style="color:#e6db74">&#34;time&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">EncodeTime</span>: <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">ISO8601TimeEncoder</span>,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">CallerKey</span>:    <span style="color:#e6db74">&#34;caller&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">EncodeCaller</span>: <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">ShortCallerEncoder</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">Build</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message with fields&#34;</span>, <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;region&#34;</span>, <span style="color:#e6db74">&#34;us-west&#34;</span>), <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#ae81ff">2</span>))
</span></span></code></pre></div><p>Will output:</p>
<pre tabindex="0"><code>{&#34;level&#34;:&#34;INFO&#34;,&#34;time&#34;:&#34;2018-05-02T16:37:54.998-0700&#34;,&#34;caller&#34;:&#34;customlogger/main.go:91&#34;,&#34;message&#34;:&#34;This is an INFO message with fields&#34;,&#34;region&#34;:&#34;us-west&#34;,&#34;id&#34;:2}
</code></pre><h1 id="3-metadata-field-encoder-alternatives">3. Metadata field encoder alternatives</h1>
<p>Each of the encoder can be customized to fit your requirements, and some have
different implementations provided by zap.</p>
<ul>
<li>timestamp can be output in either <a href="https://godoc.org/go.uber.org/zap/zapcore#ISO8601TimeEncoder">ISO 8601
format</a>, or as an epoch timestamp in
<a href="https://godoc.org/go.uber.org/zap/zapcore#EpochTimeEncoder">seconds</a>,
<a href="https://godoc.org/go.uber.org/zap/zapcore#EpochMillisTimeEncoder">milliseconds</a> and even
<a href="https://godoc.org/go.uber.org/zap/zapcore#EpochNanosTimeEncoder">nanoseconds</a>.
<br></li>
<li>level can be <a href="https://godoc.org/go.uber.org/zap/zapcore#CapitalLevelEncoder">capital</a> or
<a href="https://godoc.org/go.uber.org/zap/zapcore#LowercaseLevelEncoder">lowercase</a>. Each of them even
have <a href="https://godoc.org/go.uber.org/zap/zapcore#LowercaseColorLevelEncoder">colored</a> options.
Note that the colored options don&rsquo;t make sense in the JSON output encoder because the terminal
escape codes are not stripped in the current implementation.
<br></li>
<li>The caller can be shown in <a href="https://godoc.org/go.uber.org/zap/zapcore#ShortCallerEncoder">short</a>
and <a href="https://godoc.org/go.uber.org/zap/zapcore#FullCallerEncoder">full</a> formats.</li>
</ul>
<h1 id="4-changing-logger-behavior-on-the-fly">4. Changing logger behavior on the fly</h1>
<p>Loggers can be cloned from an existing logger with certain modification to their
behavior. This can often be useful for example, when you want to reduce code
duplication by fixing a standard set of fields the logger will always output.</p>
<ul>
<li><code>logger.AddCaller()</code> adds caller annotation</li>
<li><code>logger.AddStacktrace()</code> adds stacktraces for messages at and above a given
level</li>
<li><code>logger.Fields()</code> adds specified fields to all messages output by the new logger. Creating loggers
this way, and not specifying additional fields during the actual log call can make your logging faster with less memory allocations.</li>
<li><code>logger.WrapCore()</code> allows you to modify or even completely replace the
underlying <em>core</em> in the logger which combines the encoder, level and sink. Here is an example:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;\n*** Using a JSON encoder, at debug level, sending output to stdout, all possible keys specified\n\n&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">cfg</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Encoding</span>:         <span style="color:#e6db74">&#34;json&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Level</span>:            <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewAtomicLevelAt</span>(<span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">DebugLevel</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">OutputPaths</span>:      []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;stderr&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ErrorOutputPaths</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;stderr&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">EncoderConfig</span>: <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">EncoderConfig</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">MessageKey</span>: <span style="color:#e6db74">&#34;message&#34;</span>,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">LevelKey</span>:    <span style="color:#e6db74">&#34;level&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">EncodeLevel</span>: <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">CapitalLevelEncoder</span>,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">TimeKey</span>:    <span style="color:#e6db74">&#34;time&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">EncodeTime</span>: <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">ISO8601TimeEncoder</span>,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">CallerKey</span>:    <span style="color:#e6db74">&#34;caller&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">EncodeCaller</span>: <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">ShortCallerEncoder</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">Build</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;\n*** Same logger with console logging enabled instead\n\n&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">WithOptions</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">WrapCore</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">Core</span>) <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">Core</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">NewCore</span>(<span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">NewConsoleEncoder</span>(<span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">EncoderConfig</span>), <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">AddSync</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>), <span style="color:#a6e22e">zapcore</span>.<span style="color:#a6e22e">DebugLevel</span>)
</span></span><span style="display:flex;"><span>        })).<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message&#34;</span>)
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>*** Using a JSON encoder, at debug level, sending output to stdout, all possible keys specified

{&#34;level&#34;:&#34;INFO&#34;,&#34;time&#34;:&#34;2018-05-02T16:37:54.998-0700&#34;,&#34;caller&#34;:&#34;customlogger/main.go:90&#34;,&#34;message&#34;:&#34;This is an INFO message&#34;}

*** Same logger with console logging enabled instead

2018-05-02T16:37:54.998-0700    INFO    customlogger/main.go:99 This is an INFO message
</code></pre> ]]></content:encoded>
    </item>
    
    <item>
      <title>Using Zap - Simple use cases</title>
      <link>https://blog.sandipb.net/2018/05/02/using-zap-simple-use-cases/</link>
      <pubDate>Wed, 02 May 2018 23:37:37 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2018/05/02/using-zap-simple-use-cases/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/zap.png">
</figure>

<p>I was intrigued when Uber announced <strong><code>zap</code></strong>, a logging library for Go with <a href="https://github.com/uber-go/zap#performance">claims of really high
speed and memory efficiency</a>. I had tried structured
logging earlier using <a href="https://github.com/sirupsen/logrus">logrus</a>, but while I did not experience
it myself, I was worried by a lot of folks telling me about its performance issues at high log
volumes. So when <code>zap</code> claimed performance <em>exceeding</em> the log package from standard library, I had
to try it. Also, its flexible framework left the door open to a future plan of mine of sending logs
<code>filebeat</code> style to <a href="https://www.elastic.co/elk-stack">ELK</a>.</p>
<p>The <a href="https://godoc.org/go.uber.org/zap">documentation for the library</a> was pretty standard, but I
could not find a reasonable introduction to explore the various ways one can use the library. So I
decided to document some of my experiments with the library.</p>
<p>I collected <a href="https://github.com/sandipb/zap-examples">my code examples</a> in Github, and decided to
break it up into a series of posts.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/zap.png">
</figure>

<p>I was intrigued when Uber announced <strong><code>zap</code></strong>, a logging library for Go with <a href="https://github.com/uber-go/zap#performance">claims of really high
speed and memory efficiency</a>. I had tried structured
logging earlier using <a href="https://github.com/sirupsen/logrus">logrus</a>, but while I did not experience
it myself, I was worried by a lot of folks telling me about its performance issues at high log
volumes. So when <code>zap</code> claimed performance <em>exceeding</em> the log package from standard library, I had
to try it. Also, its flexible framework left the door open to a future plan of mine of sending logs
<code>filebeat</code> style to <a href="https://www.elastic.co/elk-stack">ELK</a>.</p>
<p>The <a href="https://godoc.org/go.uber.org/zap">documentation for the library</a> was pretty standard, but I
could not find a reasonable introduction to explore the various ways one can use the library. So I
decided to document some of my experiments with the library.</p>
<p>I collected <a href="https://github.com/sandipb/zap-examples">my code examples</a> in Github, and decided to
break it up into a series of posts.</p>
<hr>
<p><strong>Contents:</strong></p>
<!-- TOC depthfrom:1 orderedlist:false -->
<ul>
<li><a href="#trying-out-the-examples">Trying out the examples</a></li>
<li><a href="#using-logger-presets">Using logger presets</a>
<ul>
<li><a href="#using-the-example-logger-preset">Using the Example logger preset</a></li>
<li><a href="#using-the-development-logger-preset">Using the Development logger preset</a></li>
<li><a href="#using-the-production-logger-preset">Using the Production logger preset</a></li>
<li><a href="#comparing-the-presets">Comparing the presets</a></li>
</ul>
</li>
<li><a href="#using-the-sugar-logger">Using the Sugar logger</a></li>
</ul>
<!-- /TOC -->
<div class="aside-note" >
	<div class="aside-text" ><p>This post is first of a series of posts showing different ways of using <code>zap</code>. The other
posts are  <a href="https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-loggers/">Creating custom
loggers</a>, <a href="https://blog.sandipb.net/2018/05/03/using-zap-creating-custom-encoders/">Custom encoders</a> and <a href="https://blog.sandipb.net/2018/05/04/using-zap-working-with-global-loggers/">Working With Global Loggers</a></p>
<p>This documentation was written for zap v1.8.</p>
</div>
</div>
<h1 id="1-trying-out-the-examples">1. Trying out the examples</h1>
<p>For working code examples, clone my github repository, setup the environment, install zap and then
start running the examples:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ git clone https://github.com/sandipb/zap-examples.git
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ cd zap-examples
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ source env.sh
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ go get -u go.uber.org/zap
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ go run src/simple1/main.go
</span></span></code></pre></div><h1 id="2-using-logger-presets">2. Using logger presets</h1>
<p><code>zap</code> recommends using logger presets for the simplest of cases. These presets have pre-configured
log levels and output formats.</p>
<p>There are three presets currently available:</p>
<ul>
<li>Example</li>
<li>Development</li>
<li>Production</li>
</ul>
<div class="pure-u-1 aside-edit" >
	<div class="pure-u-20-24 aside-text" >The full source for the code extracts in the rest of the post is <a href="https://github.com/sandipb/zap-examples/blob/master/src/simple1/main.go">here</a></div>
</div>
<h2 id="21-using-the-example-logger-preset">2.1. Using the Example logger preset</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">logger</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewExample</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;This is a DEBUG message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message with fields&#34;</span>, <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;region&#34;</span>, <span style="color:#e6db74">&#34;us-west&#34;</span>), <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#ae81ff">2</span>))
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Warn</span>(<span style="color:#e6db74">&#34;This is a WARN message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;This is an ERROR message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// logger.Fatal(&#34;This is a FATAL message&#34;)  // would exit if uncommented</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">DPanic</span>(<span style="color:#e6db74">&#34;This is a DPANIC message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">//logger.Panic(&#34;This is a PANIC message&#34;)   // would exit if uncommented</span>
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>{&#34;level&#34;:&#34;debug&#34;,&#34;msg&#34;:&#34;This is a DEBUG message&#34;}
{&#34;level&#34;:&#34;info&#34;,&#34;msg&#34;:&#34;This is an INFO message&#34;}
{&#34;level&#34;:&#34;info&#34;,&#34;msg&#34;:&#34;This is an INFO message with fields&#34;,&#34;region&#34;:&#34;us-west&#34;,&#34;id&#34;:2}
{&#34;level&#34;:&#34;warn&#34;,&#34;msg&#34;:&#34;This is a WARN message&#34;}
{&#34;level&#34;:&#34;error&#34;,&#34;msg&#34;:&#34;This is an ERROR message&#34;}
{&#34;level&#34;:&#34;dpanic&#34;,&#34;msg&#34;:&#34;This is a DPANIC message&#34;}
</code></pre><h2 id="22-using-the-development-logger-preset">2.2. Using the Development logger preset</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewDevelopment</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;This is a DEBUG message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message with fields&#34;</span>, <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;region&#34;</span>, <span style="color:#e6db74">&#34;us-west&#34;</span>), <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#ae81ff">2</span>))
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Warn</span>(<span style="color:#e6db74">&#34;This is a WARN message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;This is an ERROR message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// logger.Fatal(&#34;This is a FATAL message&#34;)   // would exit if uncommented</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// logger.DPanic(&#34;This is a DPANIC message&#34;) // would exit if uncommented</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//logger.Panic(&#34;This is a PANIC message&#34;)    // would exit if uncommented</span>
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>2018-05-02T13:52:44.332-0700    DEBUG   simple1/main.go:28      This is a DEBUG message
2018-05-02T13:52:44.332-0700    INFO    simple1/main.go:29      This is an INFO message
2018-05-02T13:52:44.332-0700    INFO    simple1/main.go:30      This is an INFO message with fields     {&#34;region&#34;: &#34;us-west&#34;, &#34;id&#34;: 2}
2018-05-02T13:52:44.332-0700    WARN    simple1/main.go:31      This is a WARN messagemain.main
        /Users/snbhatta/dev/zap-examples/src/simple1/main.go:31
runtime.main
        /Users/snbhatta/.gradle/language/golang/1.9.2/go/src/runtime/proc.go:195
2018-05-02T13:52:44.332-0700    ERROR   simple1/main.go:32      This is an ERROR message
main.main
        /Users/snbhatta/dev/zap-examples/src/simple1/main.go:32
runtime.main
        /Users/snbhatta/.gradle/language/golang/1.9.2/go/src/runtime/proc.go:195
</code></pre><h2 id="23-using-the-production-logger-preset">2.3. Using the Production logger preset</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">NewProduction</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;This is a DEBUG message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message with fields&#34;</span>, <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;region&#34;</span>, <span style="color:#e6db74">&#34;us-west&#34;</span>), <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#ae81ff">2</span>))
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Warn</span>(<span style="color:#e6db74">&#34;This is a WARN message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;This is an ERROR message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// logger.Fatal(&#34;This is a FATAL message&#34;)   // would exit if uncommented</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">DPanic</span>(<span style="color:#e6db74">&#34;This is a DPANIC message&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// logger.Panic(&#34;This is a PANIC message&#34;)   // would exit if uncommented</span>
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>{&#34;level&#34;:&#34;info&#34;,&#34;ts&#34;:1525294364.332839,&#34;caller&#34;:&#34;simple1/main.go:43&#34;,&#34;msg&#34;:&#34;This is an INFO message&#34;}
{&#34;level&#34;:&#34;info&#34;,&#34;ts&#34;:1525294364.332864,&#34;caller&#34;:&#34;simple1/main.go:44&#34;,&#34;msg&#34;:&#34;This is an INFO message with fields&#34;,&#34;region&#34;:&#34;us-west&#34;,&#34;id&#34;:2}
{&#34;level&#34;:&#34;warn&#34;,&#34;ts&#34;:1525294364.3328729,&#34;caller&#34;:&#34;simple1/main.go:45&#34;,&#34;msg&#34;:&#34;This is a WARN message&#34;}
{&#34;level&#34;:&#34;error&#34;,&#34;ts&#34;:1525294364.332882,&#34;caller&#34;:&#34;simple1/main.go:46&#34;,&#34;msg&#34;:&#34;This is an ERROR message&#34;,&#34;stacktrace&#34;:&#34;main.main\n\t/Users/snbhatta/dev/zap-examples/src/simple1/main.go:46\nruntime.main\n\
t/Users/snbhatta/.gradle/language/golang/1.9.2/go/src/runtime/proc.go:195&#34;}
{&#34;level&#34;:&#34;dpanic&#34;,&#34;ts&#34;:1525294364.332895,&#34;caller&#34;:&#34;simple1/main.go:48&#34;,&#34;msg&#34;:&#34;This is a DPANIC message&#34;,&#34;stacktrace&#34;:&#34;main.main\n\t/Users/snbhatta/dev/zap-examples/src/simple1/main.go:48\nruntime.main\n
\t/Users/snbhatta/.gradle/language/golang/1.9.2/go/src/runtime/proc.go:195&#34;}
</code></pre><h2 id="24-comparing-the-presets">2.4. Comparing the presets</h2>
<p>By comparing the outputs you can make the following observations:</p>
<ul>
<li>Both <code>Example</code> and <code>Production</code> loggers use the <a href="https://godoc.org/go.uber.org/zap/zapcore#NewJSONEncoder">JSON
encoder</a>. <code>Development</code> uses the
<a href="https://godoc.org/go.uber.org/zap/zapcore#NewConsoleEncoder">Console</a> encoder</li>
<li>The <code>logger.DPanic()</code> function causes a panic in <code>Development</code> logger but not in <code>Example</code> or
<code>Production</code></li>
<li>The <code>Development</code> logger:
<ul>
<li>Prints a stack trace from <code>Warn</code> level and up.</li>
<li>Always prints the package/file/line number (the caller)</li>
<li>Tacks any extra fields as a json string at the end of the line</li>
<li>Prints the level names in uppercase</li>
<li>Prints timestamp in ISO8601 format with milliseconds</li>
</ul>
</li>
<li>The <code>Production</code> logger:
<ul>
<li>Doesn&rsquo;t log messages at debug level</li>
<li>Adds stack trace as a json field for Error, DPanic levels, but not for Warn</li>
<li>Always adds the caller as a json field</li>
<li>Prints timestamp in epoch format</li>
<li>Prints level names in lower case</li>
</ul>
</li>
</ul>
<h1 id="3-using-the-sugar-logger">3. Using the Sugar logger</h1>
<p>The default zap loggers expect structured tags, i.e. for every tag, you need to use a function for
the specific value type. It can sometimes feel too verbose.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;This is an INFO message with fields&#34;</span>, 
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;region&#34;</span>, <span style="color:#e6db74">&#34;us-west&#34;</span>), 
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">zap</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#ae81ff">2</span>))
</span></span></code></pre></div><p>This is the fastest option for an application where performance is important. (<em>Actually it can get
even faster if the logger has tags pre-configured, which will see in further posts in this series.</em>)</p>
<p>However, for a just <a href="https://github.com/uber-go/zap#performance">a small additional penalty</a> (which
is actually still slightly <em>better</em> than the <a href="https://golang.org/pkg/log/">standard library log
package</a>), you can use the <em>sugar</em> logger, which uses printf-style
reflection based type detection to give you a simpler syntax to add tags of mixed types.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">slogger</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Sugar</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">slogger</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;Info() uses sprint&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">slogger</span>.<span style="color:#a6e22e">Infof</span>(<span style="color:#e6db74">&#34;Infof() uses %s&#34;</span>, <span style="color:#e6db74">&#34;sprintf&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">slogger</span>.<span style="color:#a6e22e">Infow</span>(<span style="color:#e6db74">&#34;Infow() allows tags&#34;</span>, <span style="color:#e6db74">&#34;name&#34;</span>, <span style="color:#e6db74">&#34;Legolas&#34;</span>, <span style="color:#e6db74">&#34;type&#34;</span>, <span style="color:#ae81ff">1</span>)
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>2018-05-02T18:13:22.376-0700    INFO    simple1/main.go:56      Info() uses sprint
2018-05-02T18:13:22.376-0700    INFO    simple1/main.go:57      Infof() uses sprintf
2018-05-02T18:13:22.376-0700    INFO    simple1/main.go:58      Infow() allows tags     {&#34;name&#34;: &#34;Legolas&#34;, &#34;type&#34;: 1}
</code></pre><p>If you need to, you can switch from a <em>sugar</em> logger to a <em>standard</em> logger any time using the
<code>.Desugar()</code> method on the logger.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Deploying Hugo With Netlify</title>
      <link>https://blog.sandipb.net/2018/01/28/deploying-hugo-with-netlify/</link>
      <pubDate>Sun, 28 Jan 2018 21:33:56 -0800</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2018/01/28/deploying-hugo-with-netlify/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/netlify.png" width="50%">
</figure>

<p>I admit I had not paid much attention to <a href="https://www.netlify.com/">Netlify</a> earlier. It sort of seemed like yet another web performance related startup.</p>
<p>But on reading <a href="https://twitter.com/fatih">Fatih&rsquo;s</a> article on <a href="https://arslan.io/2017/11/30/notes-about-migrating-to-hugo/">hosting Hugo on Netlify</a>, it piqued my interest. A CDN/hosting service which puts your content in caches all around the world, and triggers Hugo (and bunch of other common scripts) on Github commits? And all this for free? Sounds too good to be true, and memories of <a href="posterous">Posterous</a> floated in my mind.</p>
<p>But again, the best part of using static blogging software like <a href="https://gohugo.io/">Hugo</a>, is that there is so less to lose from trying out a new hosting option - no databases to setup, no old content to migrate.</p>
<p>And so i decided to try it out as well. And it turned out to be blindingly simple! Netlify turned out to be awesome!</p>
<p>Here are all the stuff I needed to do to move my Hugo hosting from my shared hosting account at <a href="https://www.dreamhost.com/">Dreamhost</a> to Netlify.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/netlify.png" width="50%">
</figure>

<p>I admit I had not paid much attention to <a href="https://www.netlify.com/">Netlify</a> earlier. It sort of seemed like yet another web performance related startup.</p>
<p>But on reading <a href="https://twitter.com/fatih">Fatih&rsquo;s</a> article on <a href="https://arslan.io/2017/11/30/notes-about-migrating-to-hugo/">hosting Hugo on Netlify</a>, it piqued my interest. A CDN/hosting service which puts your content in caches all around the world, and triggers Hugo (and bunch of other common scripts) on Github commits? And all this for free? Sounds too good to be true, and memories of <a href="posterous">Posterous</a> floated in my mind.</p>
<p>But again, the best part of using static blogging software like <a href="https://gohugo.io/">Hugo</a>, is that there is so less to lose from trying out a new hosting option - no databases to setup, no old content to migrate.</p>
<p>And so i decided to try it out as well. And it turned out to be blindingly simple! Netlify turned out to be awesome!</p>
<p>Here are all the stuff I needed to do to move my Hugo hosting from my shared hosting account at <a href="https://www.dreamhost.com/">Dreamhost</a> to Netlify.</p>
<hr>
<p><strong>Steps:</strong></p>
<ol>
<li><a href="#create-account-and-link-to-github">Create account and link to Github</a></li>
<li><a href="#setting-up-a-custom-domain">Setting up a custom domain</a></li>
<li><a href="#setting-up-ssl">Setting up SSL</a></li>
<li><a href="#setting-up-preview-website">Setting up preview website</a></li>
<li><a href="#tweaks-for-hugo">Tweaks for Hugo</a></li>
</ol>
<h2 id="create-account-and-link-to-github">Create account and link to Github</h2>
<p>I first created an account of Netlify, and after logging in, I set up access to Github. If you use the &ldquo;<em>Login using Github</em>&rdquo; option while signing up, I suspect that both the account creation and Github linking will happen at the same time. I prefer using a distinct email based account in every site, for various reasons.</p>
<p>After linking to Github, there was an option presented to choose an existing repository to pick up changes from, and since I was already pushing the <a href="https://github.com/sandipb/blog.sandipb.net">source of my Hugo site</a> to Github, this wasn&rsquo;t much trouble. I was asked the command to build (I used <code>hugo</code>), and the directory where the static content is to be generated (I used the default <code>public/</code>). And that was it!</p>
<p>Netlify picked up my changes and created a random host name under the <code>netlify.com</code> and published it there!</p>
<figure><img src="/images/netlify-master-publish.png"
    alt="Netlify: Master publish">
</figure>

<p>An interesting thing is that my Hugo source uses themes as <a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules">Git submodules</a>. Netlify checked out these theme submodules before running Hugo. Neat!</p>
<h2 id="setting-up-a-custom-domain">Setting up a custom domain</h2>
<p>Netlify lets you point a custom domain to your hosted website for free! Crazy! All I had to do was to log in to my DNS provider, remove the A record for my earlier website, and add a CNAME instead to the random hostname on which Netlify was hosting my website.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ dig  blog.sandipb.net
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>...
</span></span><span style="display:flex;"><span>;; ANSWER SECTION:
</span></span><span style="display:flex;"><span>blog.sandipb.net.   3582    IN  CNAME   awesome-blackwell-069f9b.netlify.com.
</span></span><span style="display:flex;"><span>awesome-blackwell-069f9b.netlify.com. 2 IN A    35.199.180.1
</span></span></code></pre></div><p>Within a few minutes, I could already see my content on Netlify! I tried to use a curl from various servers to believe my eyes!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ curl -sI https://blog.sandipb.net/
</span></span><span style="display:flex;"><span>HTTP/1.1 200 OK
</span></span><span style="display:flex;"><span>Cache-Control: public, max-age=0, must-revalidate
</span></span><span style="display:flex;"><span>Content-Length: 0
</span></span><span style="display:flex;"><span>Content-Type: text/html; charset=UTF-8
</span></span><span style="display:flex;"><span>Date: Mon, 29 Jan 2018 06:02:15 GMT
</span></span><span style="display:flex;"><span>Etag: &#34;98934c8fe85d0e8c73379da28cedef89-ssl&#34;
</span></span><span style="display:flex;"><span>Strict-Transport-Security: max-age=31536000
</span></span><span style="display:flex;"><span>Age: 1
</span></span><span style="display:flex;"><span>Connection: keep-alive
</span></span><span style="display:flex;"><span>Server: Netlify          &lt;&lt;&lt;&lt;-------
</span></span></code></pre></div><p>As an aside, you can change the hostname under netlify.com domain as well, but since the only characters allowed as alphanumeric+underscore, you can&rsquo;t get too creative here. Interestingly, even though I changed my Netlify hostname, the DNS setup didn&rsquo;t break. Netlify still keeps the original random hostname alive. Nice.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell-session" data-lang="shell-session"><span style="display:flex;"><span>$ dig +short blog.sandipb.net
</span></span><span style="display:flex;"><span>awesome-blackwell-069f9b.netlify.com.
</span></span><span style="display:flex;"><span>104.198.106.181
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ dig +short awesome-blackwell-069f9b.netlify.com.
</span></span><span style="display:flex;"><span>104.198.106.181
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ dig +short blog-sandipb-net.netlify.com.
</span></span><span style="display:flex;"><span>104.198.106.181
</span></span></code></pre></div><p>All this was nice and good, but there were a few more details to sew up!</p>
<h2 id="setting-up-ssl">Setting up SSL</h2>
<p>Just like my previous hosting provider, Dreamhost and many other hosting providers nowadays, Netlify has automated <a href="https://letsencrypt.org/">Lets Encrypt SSL</a> sourcing and installation features. Within a few minutes, my site was running on SSL.</p>
<p>BTW, if you choose to only use your <em>xxx.netlify.com</em> hostname, you don&rsquo;t need this, because Netlify uses a wild-carded SSL certificate for all their sub-domains.</p>
<p>There was also an option to force HTTPS for the website, which I of course turned on.</p>
<p>Chrome showed a niggling security warning about a resource being insecure, and I hunted down and found out that the badge I pull in from Feedburner had a non-https URL. Fixing that and committing to Github, caused Netlify to rebuild, and within a few minutes, the site was completely free of SSL warnings.</p>
<h2 id="setting-up-preview-website">Setting up preview website</h2>
<p>A fantastic feature that Netlify offers, again for free, was an automatic staging website to preview pull requests and branch changes.</p>
<p>If you have a pull request on your master branch, there would automatically be a build done on it, and made viewable on a unique hostname created from the commit id. You can view the final changes, and proceed to Github to merge it in.</p>
<div class="aside-note" >
	<div class="aside-text" ><em>Documentation:</em>  <a href="https://www.netlify.com/blog/2016/07/20/introducing-deploy-previews-in-netlify/">Introducing Deploy Previews in Netlify</a></div>
</div>
<p>You also have the option to set up one or more dev/staging branches, all of which will get automatic previews on every commit.</p>
<p>There were some niggling UI issue in configuring this though. The UI provides a weird combination of dropdown+text field to specify the branches. The dropdown is empty by default, you are actually expected to type in the branch name, even if it already exists on the repository.</p>
<p>In any case, previews will start happening from the <em>next</em> commit to the branch, and not automatically from the present content.</p>
<h2 id="tweaks-for-hugo">Tweaks for Hugo</h2>
<p>As Fatih mentions in his post, there is an apparent conflict between Hugo configuration and how the preview system works.</p>
<p>You might be using a baseurl of <code>/</code> for links to work locally using relative URLs. But you might want the production baseurl to be that of the website address so that all links are absolute.</p>
<p>This doesn&rsquo;t work very well with the default set up in Netlify because the domain name for previews are random, and the production baseurl is different.</p>
<p>One way Fatih suggests is to use Netlify configuration files. These files allow you to change environment variables based on the deployment context - <em>production</em> or <em>deploy-preview</em>.</p>
<div class="aside-note" >
	<div class="aside-text" >Hugo allows all its main site configuration variables to be overridden by environment variables (<a href="https://gohugo.io/getting-started/configuration/#environmental-variables">Reference</a>).</div>
</div>
<p>Also, Netlify allows you to specify the Hugo version to use for building.</p>
<div class="aside-note" >
	<div class="aside-text" >Article: <a href="https://www.netlify.com/blog/2017/04/11/netlify-plus-hugo-0.20-and-beyond/">Netlify Plus Hugo 0.20 and Beyond</a></div>
</div>
<p>Since I mostly use Hugo via <a href="https://brew.sh/">Homebrew</a> which keeps the Hugo version pretty up to date, I specified the Hugo version in the configuration file as well.</p>
<p>So this is the final <code>netlify.toml</code> file that I currently use, which is pretty much the same that Fatih proposed.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">production</span>.<span style="color:#a6e22e">environment</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">HUGO_VERSION</span> = <span style="color:#e6db74">&#34;0.30&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">HUGO_BASEURL</span> = <span style="color:#e6db74">&#34;https://blog.sandipb.net/&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">deploy-preview</span>.<span style="color:#a6e22e">environment</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">HUGO_VERSION</span> = <span style="color:#e6db74">&#34;0.30&#34;</span>
</span></span></code></pre></div><hr>
<p><strong>Update</strong>: One thing I found out, was that while each pull request and each commit to a <em>non-master</em> branch got an unique hostname for the preview, Netlify also maintains a constant hostname for every branch. For example, for my <code>dev</code> branch on the website <code>blog-sandipb-net.netlify.com</code>, there is always a preview site at <a href="https://dev--blog-sandipb-net.netlify.com/">https://dev--blog-sandipb-net.netlify.com/</a> with a build off the top of this branch. So, I could actually make absolute links work in the deploy-preview environment as well by specifying the <code>HUGO_BASEURL</code> environment variable in <code>netlify.toml</code>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Settling on Vscode for Go</title>
      <link>https://blog.sandipb.net/2018/01/14/settling-on-vscode-for-go/</link>
      <pubDate>Sun, 14 Jan 2018 02:21:27 -0800</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2018/01/14/settling-on-vscode-for-go/</guid>
      <description><![CDATA[<p>Soon after <a href="/2017/11/06/jetbrains-finalizes-name-for-their-go-ide/">I reviewed</a> <a href="https://www.jetbrains.com/go/">GoLand</a>, I discovered <a href="https://code.visualstudio.com/">VS Code</a> - a general purpose editor with
superlative <a href="https://code.visualstudio.com/docs/languages/go">support for Go</a>. And I have been impressed enough to
stay.</p>
<figure><img src="/images/vscodesignaturehelp.png"><figcaption>
      <h4>VSCode</h4>
    </figcaption>
</figure>]]></description>
      <content:encoded><![CDATA[      <p>Soon after <a href="/2017/11/06/jetbrains-finalizes-name-for-their-go-ide/">I reviewed</a> <a href="https://www.jetbrains.com/go/">GoLand</a>, I discovered <a href="https://code.visualstudio.com/">VS Code</a> - a general purpose editor with
superlative <a href="https://code.visualstudio.com/docs/languages/go">support for Go</a>. And I have been impressed enough to
stay.</p>
<figure><img src="/images/vscodesignaturehelp.png"><figcaption>
      <h4>VSCode</h4>
    </figcaption>
</figure>

<p>I admit, I had shied away from trying out this editor for so long even though I had heard good things about it -
partly because of its Microsoft heritage (I am an old <a href="http://linux-delhi.org/">LUG</a> guy after all), its Visual Studio
heritage (a really resource hungry IDE, from a single really brief encounter several years back) and its Electron
heritage (embedded browsers haven&rsquo;t really impressed me in performance). &#x1f604;</p>
<p>But VS Code has really really impressed me.</p>
<ul>
<li>Its Go plugin works really well out of the box.</li>
<li>It has out of the box support for Git in the core</li>
<li>Has a really refined global/workspace/folder settings hierarchy,</li>
<li>an embedded terminal which is really usable</li>
<li>A very responsive UI, probably the fastest JS (Actually TS, nevertheless) based editor that I have seen</li>
</ul>
<p>And best of all &hellip; it &hellip; is &hellip; free!</p>
<figure class="aligncenter"><img src="/images/vscode-diff-review-pane.png">
</figure>
<p>Much as I love <a href="https://www.jetbrains.com/pycharm/">Pycharm</a>, I love the <a href="https://www.sublimetext.com/">Sublime Text</a>
model of pricing more - a single one time pricing for a major version, not an annual one.</p>
<p>And with Microsoft funding full time developers on the project, there is amazing support! Updates are frequent! I
actually have an anecdote about the super support by the team.</p>
<p>I had tweeted sometime back about the inability to override <code>GOPATH</code> at the folder level, now that VS code supports
workspaces with multiple folders in a workspace.</p>

<p>Ramya from the dev team noticed my tweet on a Sunday and I clarified the use case in a follow up tweet.</p>

<p>I then <a href="https://github.com/Microsoft/vscode-go/issues/1400">filed a Github issue</a>, and within 4 hours on a weekend,
she had a commit out!</p>
<p>I was sold. Even for a paid software this is amazing, and this is free!</p>
<p>I don&rsquo;t like being split between editors. I already use Sublime Text 3 for my adhoc editing work (I am typing this in
it right now), Pycharm for Python - both with personal licences. I can afford to have another editor for a specific
language - I am not even considering the irritable matter of paying for two editors from the same company (no, the <a href="https://www.jetbrains.com/idea/buy/#edition=personal">
all inclusive package</a> from them is way out of my budget). Now I
am trying to see if I can move my Python work to this
editor - so far it seems pretty encouraging, it has pretty good support for virtualenvs. I need to spend more time on
it though - Pycharm has set a pretty high bar.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Go Static Typing &#39;Magic&#39;</title>
      <link>https://blog.sandipb.net/2017/11/13/go-static-typing-magic/</link>
      <pubDate>Mon, 13 Nov 2017 16:31:32 -0800</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2017/11/13/go-static-typing-magic/</guid>
      <description><![CDATA[<p>As I understand Go more, some of the concepts tend to make my head hurt. Sometimes, innocent examples
in various tutorials hide such deep concepts, that it takes a while for me to decode it all.</p>
<p>Here is an example. In various tutorials, pauses are made using <a href="https://golang.org/pkg/time/#Sleep">time.Sleep()</a>.</p>
<p>The first time I saw an example like the following, it made me stop in my tracks.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">100</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>]]></description>
      <content:encoded><![CDATA[      <p>As I understand Go more, some of the concepts tend to make my head hurt. Sometimes, innocent examples
in various tutorials hide such deep concepts, that it takes a while for me to decode it all.</p>
<p>Here is an example. In various tutorials, pauses are made using <a href="https://golang.org/pkg/time/#Sleep">time.Sleep()</a>.</p>
<p>The first time I saw an example like the following, it made me stop in my tracks.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">100</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Here is the signature of <code>time.Sleep</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Sleep</span>(<span style="color:#a6e22e">d</span> <span style="color:#a6e22e">Duration</span>)</span></span></code></pre></div>
<p>And here is what <code>Duration</code> and <code>time.Millisecond</code> means.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Duration</span> <span style="color:#66d9ef">int64</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Nanosecond</span>  <span style="color:#a6e22e">Duration</span> = <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Microsecond</span>          = <span style="color:#ae81ff">1000</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">Nanosecond</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Millisecond</span>          = <span style="color:#ae81ff">1000</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">Microsecond</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Second</span>               = <span style="color:#ae81ff">1000</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">Millisecond</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Minute</span>               = <span style="color:#ae81ff">60</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">Second</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Hour</span>                 = <span style="color:#ae81ff">60</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">Minute</span>
</span></span><span style="display:flex;"><span>)</span></span></code></pre></div>
<p>Coming from my past experience with other languages, my question was, in a strongly typed language like Go:</p>
<ol>
<li>How does <code>100 * time.Millisecond</code> work? Aren&rsquo;t they entirely two different types?
And I know that <a href="https://golang.org/doc/faq#overloading">Go doesn&rsquo;t support operator overloading</a>. So how does Go know how to use
the operator <code>*</code> between a <code>Duration</code> and what looks like an <code>int</code>. My first guess was that Go was converting
the duration <code>time.Millisecond</code> to an <code>int</code> to make the multiplication work, like in other language.</li>
<li>Even if somehow the multiplication worked, how is the result satisfying the type requirement for the parameter
passed to <code>Sleep()</code> which requires a <code>Duration</code>.</li>
</ol>
<p>Turns out my guess was wrong.</p>
<p>There are a couple of points in the <a href="https://golang.org/ref/spec">spec</a> that explains what is  happening.</p>
<ul>
<li>
<p>Since <code>Duration</code> is of type <code>int64</code>, this confirms that a <code>Duration</code> and a <code>int64</code> are different even though
the latter is the <em>underlying type</em> of the former.</p>



  <blockquote>
    <p>A type definition creates a new, distinct type with the same underlying type and
operations as the given type, and binds an identifier to it. The new type is called a
defined type. It is different from any other type, including the type it is created from.</p>

  </blockquote>

</li>
<li>
<p>However, <strong>type identity</strong> is the most crucial part which establishes that <code>int64</code> and <code>Duration</code> as considered to be
<em>identical type</em> whenever the question arises.</p>



  <blockquote>
    <p>A defined type is always different from any other type. Otherwise, two types are
identical if their underlying type literals are structurally equivalent; that is,
they have the same literal structure and corresponding components have identical types.</p>

  </blockquote>

</li>
<li>
<p>However, this is the missing link I was looking for. This is how <code>100 * time.Millisecond</code>
becomes <code>time.Duration(100) * time.Millisecond</code></p>



  <blockquote>
    <p>For other binary operators, the operand types must be identical unless the operation involves
shifts or untyped constants. &hellip; Except for shift operations, if one operand is an untyped
constant and the other operand is not, the constant is converted to the type of the other operand.</p>

  </blockquote>

</li>
<li>
<p>And what happens after the multiplication? The result is considered to be of type <code>time.Duration</code></p>



  <blockquote>
    <p>Arithmetic operators apply to numeric values and yield a result of the same type as the first operand.</p>

  </blockquote>

</li>
</ul>
<p>So, if I have got it right:</p>
<ol>
<li>Go will convert the untyped integer value <code>100</code> to the type <code>Duration</code>.</li>
<li>Since both have the underlying type of integer, it will follow the integer operator rules for <code>*</code></li>
<li>The result will be converted to the type of <code>time.Duration</code></li>
</ol> ]]></content:encoded>
    </item>
    
    <item>
      <title>Jetbrains Finalizes Name for their Go IDE</title>
      <link>https://blog.sandipb.net/2017/11/06/jetbrains-finalizes-name-for-their-go-ide/</link>
      <pubDate>Mon, 06 Nov 2017 00:45:55 -0800</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2017/11/06/jetbrains-finalizes-name-for-their-go-ide/</guid>
      <description><![CDATA[<p>Seems like <a href="https://blog.jetbrains.com/go/2017/11/02/announcing-goland-former-gogland-eap-18-final-product-name-templates-support-and-more/">Jetbrains has finally ditched</a> that weird name for their Go IDE and changed it to a
more palatable, but not really very inventive version (come on, I think <em>PyCharm</em> is a pretty nice
name for a Python editor).
Gogland is now <strong>GoLand</strong>!</p>
<p><img src="/images/goland-screenshot.png" alt="New goland logo"></p>]]></description>
      <content:encoded><![CDATA[      <p>Seems like <a href="https://blog.jetbrains.com/go/2017/11/02/announcing-goland-former-gogland-eap-18-final-product-name-templates-support-and-more/">Jetbrains has finally ditched</a> that weird name for their Go IDE and changed it to a
more palatable, but not really very inventive version (come on, I think <em>PyCharm</em> is a pretty nice
name for a Python editor).
Gogland is now <strong>GoLand</strong>!</p>
<p><img src="/images/goland-screenshot.png" alt="New goland logo"></p>
<p>Well, the good news doesn&rsquo;t stop there!</p>
<ul>
<li>We now have Go template editing features</li>
<li>Several nifty updates to the editor, like reminder/wizard for adding comments to public exported symbols, empty slice warning/wizard</li>
</ul>
<p>And we <em>finally</em> have a release date and pricing.</p>



  <blockquote>
    <p>GoLand 2017.3 is going to be released in early December. GoLand will join the ‘All Products’ pack and will be priced
exactly the same as PyCharm, PhpStorm, RubyMine, DataGrip, CLion, and AppCode.</p>

  </blockquote> ]]></content:encoded>
    </item>
    
    <item>
      <title>404: Not found</title>
      <link>https://blog.sandipb.net/404-not-found/</link>
      <pubDate>Sun, 05 Nov 2017 19:17:00 +0800</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/404-not-found/</guid>
      <description><![CDATA[<p align="center">
Sorry. I have nothing for you at this URL.
<figure>
	<img 
	src="/images/Sand-Dune-Dry-Landscape-Morocco-Desert-1270345.jpeg" 
	alt="Sand dunes, Morocco" 
	/>
	<figcaption style="font-size: 0.7em;text-align: center;">Image credit: <a href="http://maxpixel.freegreatpicture.com/Sand-Dune-Dry-Landscape-Morocco-Desert-1270345">Max Pixel</a></figcaption>
</figure>
</p>]]></description>
      <content:encoded><![CDATA[      <p align="center">
Sorry. I have nothing for you at this URL.
<figure>
	<img 
	src="/images/Sand-Dune-Dry-Landscape-Morocco-Desert-1270345.jpeg" 
	alt="Sand dunes, Morocco" 
	/>
	<figcaption style="font-size: 0.7em;text-align: center;">Image credit: <a href="http://maxpixel.freegreatpicture.com/Sand-Dune-Dry-Landscape-Morocco-Desert-1270345">Max Pixel</a></figcaption>
</figure>
</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Troubles With Hugo as Well</title>
      <link>https://blog.sandipb.net/2017/11/05/troubles-with-hugo-as-well/</link>
      <pubDate>Sun, 05 Nov 2017 01:35:42 -0800</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2017/11/05/troubles-with-hugo-as-well/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/hugo.png"><figcaption>
      <h4>Hugo</h4>
    </figcaption>
</figure>

<p>I have been using <a href="https://gohugo.io/">Hugo</a> as a static website generator for a while. I love the speed, coming from
its Go origins. I love a static website generator for the peace-of-mind it gives me (No <em>did I forget to update my XXX
blog software after that bug came out?</em> ).</p>
<p>But of course, it is not all peachy.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/hugo.png"><figcaption>
      <h4>Hugo</h4>
    </figcaption>
</figure>

<p>I have been using <a href="https://gohugo.io/">Hugo</a> as a static website generator for a while. I love the speed, coming from
its Go origins. I love a static website generator for the peace-of-mind it gives me (No <em>did I forget to update my XXX
blog software after that bug came out?</em> ).</p>
<p>But of course, it is not all peachy.</p>
<p>First of all, it still requires me to keep my theme in sync with the theme author, just in case he has fixed some bugs
that you saw. Theme management is best done in Hugo using <a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules">git submodules</a>
which is its own bundle of fun. And if you made some changes to the theme of your own, keeping your changes merged well with
the upstream theme author is just the cherry on the top.</p>
<p>Now I just need to figure out where this error is coming from :-/</p>
<pre tabindex="0"><code>  ERROR 2017/11/05 01:49:56 Page&#39;s Now is deprecated and will be removed in Hugo 0.31. Use now (the template func).
</code></pre><p><strong>Update:</strong> Ok, my comment above was a bit uninformed. The right way to make modifications to a theme in Hugo is not to edit the
theme code itself, but to <em>override</em> it. I just switched my theme to <a href="https://github.com/yoshiharuyamashita/blackburn">Blackburn</a>
and this time I took care in ensuring that the stuff I wanted modified are made in a way it is overridden.</p>
<p>So for CSS styles, like the blockquotes in this blog, were overriden using a custom local CSS file. The theme actually has a rarely
provided config to specify the css file name used to override the theme styles. This made it pretty easy</p>
<p>In case a theme doesn&rsquo;t provide that, I would have needed to copy the partial template corresponding to the html head to
the local site file hierarchy, and edit that. <a href="https://gohugo.io/themes/customizing/">The docs explain this pretty well</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Lahaia noon at hawaii</title>
      <link>https://blog.sandipb.net/2017/04/28/lahaia-noon-at-hawaii/</link>
      <pubDate>Fri, 28 Apr 2017 00:44:12 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2017/04/28/lahaia-noon-at-hawaii/</guid>
      <description><![CDATA[<blockquote class="imgur-embed-pub" lang="en" data-id="ntI9jgA"><a href="//imgur.com/ntI9jgA">When the sun is directly overhead in Hawaii, it looks like a bad video game render</a></blockquote><script async src="//s.imgur.com/min/embed.js" charset="utf-8"></script>



  <blockquote>
    <p>The sun is exactly overhead twice a year in Lahaina, Hawaii, once in May and
once in July. Poles don&rsquo;t cast shadows, giving the urban landscape an eerie
appearance. Hawaii is the only state in the US where the sun&rsquo;s rays are
perpendicular to the surface of Earth. It&rsquo;s called a <a href="https://en.wikipedia.org/wiki/Subsolar_point">subsolar
point</a>.</p>

  </blockquote>

<p>Via <a href="http://boingboing.net/2017/04/13/lahaina-noon-when-the-sun-is.html">Boingboing</a></p>]]></description>
      <content:encoded><![CDATA[      <blockquote class="imgur-embed-pub" lang="en" data-id="ntI9jgA"><a href="//imgur.com/ntI9jgA">When the sun is directly overhead in Hawaii, it looks like a bad video game render</a></blockquote><script async src="//s.imgur.com/min/embed.js" charset="utf-8"></script>



  <blockquote>
    <p>The sun is exactly overhead twice a year in Lahaina, Hawaii, once in May and
once in July. Poles don&rsquo;t cast shadows, giving the urban landscape an eerie
appearance. Hawaii is the only state in the US where the sun&rsquo;s rays are
perpendicular to the surface of Earth. It&rsquo;s called a <a href="https://en.wikipedia.org/wiki/Subsolar_point">subsolar
point</a>.</p>

  </blockquote>

<p>Via <a href="http://boingboing.net/2017/04/13/lahaina-noon-when-the-sun-is.html">Boingboing</a></p> ]]></content:encoded>
    </item>
    
    <item>
      <title>The Borg design pattern</title>
      <link>https://blog.sandipb.net/2017/04/27/the-borg-design-pattern/</link>
      <pubDate>Thu, 27 Apr 2017 02:11:10 -0700</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2017/04/27/the-borg-design-pattern/</guid>
      <description><![CDATA[<p>How to have shared state between different instance of a class without a singleton pattern.</p>



  <blockquote>
    <p>The &lsquo;Singleton&rsquo; DP is all about ensuring that just one instance of a certain
class is ever created. It has a catchy name and is thus enormously popular,
but it&rsquo;s NOT a good idea &ndash; it displays different sorts of problems in
different object-models. What we should really WANT, typically, is to let as
many instances be created as necessary, BUT all with shared state. Who cares
about identity &ndash; it&rsquo;s state (and behavior) we care about!</p>

  </blockquote>

<p>By Alex Martelli at <a href="http://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/">Singleton? We Don&rsquo;t Need No Stinkin&rsquo; Singleton: The Borg Design Pattern</a>.</p>]]></description>
      <content:encoded><![CDATA[      <p>How to have shared state between different instance of a class without a singleton pattern.</p>



  <blockquote>
    <p>The &lsquo;Singleton&rsquo; DP is all about ensuring that just one instance of a certain
class is ever created. It has a catchy name and is thus enormously popular,
but it&rsquo;s NOT a good idea &ndash; it displays different sorts of problems in
different object-models. What we should really WANT, typically, is to let as
many instances be created as necessary, BUT all with shared state. Who cares
about identity &ndash; it&rsquo;s state (and behavior) we care about!</p>

  </blockquote>

<p>By Alex Martelli at <a href="http://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/">Singleton? We Don&rsquo;t Need No Stinkin&rsquo; Singleton: The Borg Design Pattern</a>.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Documenting both class and constructor in Sphinx</title>
      <link>https://blog.sandipb.net/2016/10/10/documenting-both-class-and-constructor-in-sphinx/</link>
      <pubDate>Mon, 10 Oct 2016 01:23:05 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2016/10/10/documenting-both-class-and-constructor-in-sphinx/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/2016/10/sphinx.png">
</figure>
<p>I lost an hour of my life today trying to figure out why Sphinx would not document a class inside a module specified by
<a href="http://www.sphinx-doc.org/en/stable/ext/autodoc.html#directive-automodule">:automodule:</a>.</p>
<p>I learnt two things today.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/2016/10/sphinx.png">
</figure>
<p>I lost an hour of my life today trying to figure out why Sphinx would not document a class inside a module specified by
<a href="http://www.sphinx-doc.org/en/stable/ext/autodoc.html#directive-automodule">:automodule:</a>.</p>
<p>I learnt two things today.</p>
<p>Firstly, Sphinx will not document a class automatically as part of <code>:automodule:</code> if the class itself doesn&rsquo;t have a
docstring. You can force the class to be displayed by having an additional <code>:autoclass:</code>directive though.</p>
<p>If the class does have a doc string, it will be displayed including the documentation of its regular (ones without a
leading <code>_</code> in the name) methods. It will still not show the constructor though.</p>
<p>To display the constructor, I first thought I would add the <code>:special-members:</code> option to the <code>:automodule:</code> directive.
But that didn&rsquo;t work very well, as it showed various unnecessary private functions as well, e.g. <code>_weakref</code>.</p>
<p>I finally discovered that the best way to get the constructor documentation displayed was using the
<a href="http://www.sphinx-doc.org/en/stable/ext/autodoc.html#confval-autoclass_content">autoclass_content</a> config in the
sphinx config file.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>autoclass_content <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;both&#39;</span></span></span></code></pre></div>
<p>As per the documentation,</p>



  <blockquote>
    <p>This value selects what content will be inserted into the main body of an autoclass directive. The possible values
are:</p>
<ul>
<li>&ldquo;class&rdquo;: Only the class’ docstring is inserted. This is the default. You
&ldquo;can still document <strong>init</strong> as a separate method using automethod or
&ldquo;the members option to autoclass.</li>
<li>&ldquo;both&rdquo;: Both the class’ and the <strong>init</strong> method’s docstring are concatenated and inserted.</li>
<li>&ldquo;init&rdquo;: Only the <strong>init</strong> method’s docstring is inserted.</li>
</ul>

  </blockquote> ]]></content:encoded>
    </item>
    
    <item>
      <title>Using Python to update a required field while performing a transition in Jira</title>
      <link>https://blog.sandipb.net/2016/02/19/using-python-to-update-a-required-field-while-performing-a-transition-in-jira/</link>
      <pubDate>Fri, 19 Feb 2016 03:08:17 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2016/02/19/using-python-to-update-a-required-field-while-performing-a-transition-in-jira/</guid>
      <description><![CDATA[<figure class="alignright"><a href="https://www.flickr.com/photos/donsolo/"><img src="/images/2016/02/gojira.jpg"
    alt="&#39;Gojira!&#39; by donsolo"></a>
</figure>

<p>This might be a very esoteric topic for most people, but since I could not find information about this anywhere, I
decided to document this in a post.</p>
<p>Here is the problem. I use <a href="https://www.atlassian.com/software/jira">Jira</a> at work, and today, I needed to close a bunch
of tickets based on a search result. Now, searching or doing batch operations is simple enough from the browser, but a
small detail made the exercise impossible via the web UI.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><a href="https://www.flickr.com/photos/donsolo/"><img src="/images/2016/02/gojira.jpg"
    alt="&#39;Gojira!&#39; by donsolo"></a>
</figure>

<p>This might be a very esoteric topic for most people, but since I could not find information about this anywhere, I
decided to document this in a post.</p>
<p>Here is the problem. I use <a href="https://www.atlassian.com/software/jira">Jira</a> at work, and today, I needed to close a bunch
of tickets based on a search result. Now, searching or doing batch operations is simple enough from the browser, but a
small detail made the exercise impossible via the web UI.</p>
<p>Our Jira project requires a field to be filled while closing the ticket - the time spent in the ticket. This breaks Jira
in all sorts of ways - the batch operation doesn&rsquo;t work, some of the email-to-jira interface at work breaks as well.</p>
<p>So I looked at doing this via the <a href="https://github.com/pycontribs/jira">Jira Python library</a>. But it didn&rsquo;t work as
expected.</p>
<pre tabindex="0"><code class="language-pycon" data-lang="pycon">&gt;&gt;&gt; from jira import JIRA

&gt;&gt;&gt; jira = JIRA(&#34;https://JIRA_URL&#34;, basic_auth=(&#39;USER_NAME&#39;, &#39;Password&#39;))
&gt;&gt;&gt;
&gt;&gt;&gt; issue = jira.issue(&#34;ISSUE-123&#34;)
&gt;&gt;&gt;
&gt;&gt;&gt; [(t[&#39;id&#39;], t[&#39;name&#39;]) for t in jira.transitions(issue)]  # What are the workflows available?
[(u&#39;4&#39;, u&#39;Start Progress&#39;), (u&#39;5&#39;, u&#39;Resolve Issue&#39;), (u&#39;2&#39;, u&#39;Close Issue&#39;), (u&#39;711&#39;, u&#39;Planning&#39;), (u&#39;751&#39;, u&#39;Blocked&#39;), (u&#39;801&#39;, u&#39;To Monitor&#39;)
&gt;&gt;&gt;
&gt;&gt;&gt; jira.transition_issue(issue, &#39;2&#39;)
Traceback (most recent call last):
  File &#34;&lt;stdin&gt;&#34;, line 1, in &lt;module&gt;
...
jira.exceptions.JIRAError: JiraError HTTP 400
    text: Time Spent is required
    url: ...
    response headers = {...}
    response text = {&#34;errorMessages&#34;:[&#34;Time Spent is required&#34;],&#34;errors&#34;:{}}</code></pre>
<p>The <a href="http://jira.readthedocs.org/en/latest/#transitions">documentation on transitions</a> mentioned that we could add
fields in the call to <code>jira.transitions()</code>. That didn&rsquo;t work as well.</p>
<pre tabindex="0"><code class="language-pycon" data-lang="pycon">&gt;&gt;&gt; jira.transition_issue(issue, &#39;2&#39;, timespent=&#34;1h&#34;)
Traceback (most recent call last):
  File &#34;&lt;stdin&gt;&#34;, line 1, in &lt;module&gt;
...
jira.exceptions.JIRAError: JiraError HTTP 400
    text: Field &#39;timespent&#39; cannot be set. It is not on the appropriate screen, or unknown.
    url: ...
    response headers = ...
    response text = {&#34;errorMessages&#34;:[],&#34;errors&#34;:{&#34;timespent&#34;:&#34;Field &#39;timespent&#39; cannot be set. It is not on the appropriate screen, or unknown.&#34;}}</code></pre>
<p>So I scoured the Internet for a long time, till I found  <a href="https://answers.atlassian.com/questions/217485/transition-via-the-jira-rest-api-with-a-custom-field">this post</a> about how to do it via the <a href="https://docs.atlassian.com/jira/REST/latest/">REST
API</a> - the only official interface to Jira. Here is what needs to be sent as the body to the POST request.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;transition&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;2&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;update&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;worklog&#34;</span>: [
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">&#34;add&#34;</span>: {
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">&#34;timeSpent&#34;</span>: <span style="color:#e6db74">&#34;2m&#34;</span>
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        ]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>I felt that to be odd, till I looked at both the <a href="https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition">api documentation for transition</a> and the  <a href="http://jira.readthedocs.org/en/latest/_modules/jira/client.html#JIRA.transition_issue">doc for
transition using the python library</a> and I found out why I have not been successful till now.</p>
<p>The REST api supports two ways to update the issue while doing a transition - you can set certain fields using the
<code>fields</code> option, or you can use the <code>update</code> option to do more complex changes.</p>
<p>The comment in the Python code revealed that the <code>update</code> method has not yet been implemented.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">transition_issue</span>(self, issue, transition, fields<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>, comment<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>, <span style="color:#f92672">**</span>fieldargs):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># TODO: Support update verbs (same as issue.update())</span></span></span></code></pre></div>
<p>That put me in a bind. I had only one way to hack around this problem now - using the REST api for the specific
operation I wanted, and the Python library for the rest of the work - ugly, but works for now, till the Python library
is complete.</p>
<p>So here was the final solution that did what I wanted.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> jira <span style="color:#f92672">import</span> JIRA
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> requests
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>jira <span style="color:#f92672">=</span> JIRA(<span style="color:#e6db74">&#34;https://JIRA_URL&#34;</span>, basic_auth<span style="color:#f92672">=</span>(<span style="color:#e6db74">&#39;USER_NAME&#39;</span>, <span style="color:#e6db74">&#39;PASSWORD&#39;</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>d <span style="color:#f92672">=</span> {}
</span></span><span style="display:flex;"><span>d[<span style="color:#e6db74">&#34;transition&#34;</span>]<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;2&#34;</span>}
</span></span><span style="display:flex;"><span>d[<span style="color:#e6db74">&#34;update&#34;</span>]<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;worklog&#34;</span>: [{<span style="color:#e6db74">&#34;add&#34;</span>: {<span style="color:#e6db74">&#34;timeSpent&#34;</span>: <span style="color:#e6db74">&#34;1h&#34;</span>}}]}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>s <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>Session()
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>auth <span style="color:#f92672">=</span> (<span style="color:#e6db74">&#34;USER&#34;</span>, <span style="color:#e6db74">&#34;PASSWORD&#34;</span>)
</span></span><span style="display:flex;"><span>s<span style="color:#f92672">.</span>headers<span style="color:#f92672">.</span>update({<span style="color:#e6db74">&#34;Content-Type&#34;</span>: <span style="color:#e6db74">&#34;application/json&#34;</span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>j<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://JIRA_URL&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>issue_list <span style="color:#f92672">=</span> jira<span style="color:#f92672">.</span>search_issues(<span style="color:#e6db74">&#34;assignee = currentUser() AND resolution = Unresolved  and status != Closed and updatedDate &lt; &#39;2015-10-01&#39; and project=&#39;PROJECT&#39; ORDER BY updatedDate DESC&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> issue_list:
</span></span><span style="display:flex;"><span>    print i
</span></span><span style="display:flex;"><span>    s<span style="color:#f92672">.</span>post(j<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/rest/api/2/issue/&#34;</span><span style="color:#f92672">+</span>i<span style="color:#f92672">.</span>key<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/transitions&#34;</span>, data<span style="color:#f92672">=</span>json<span style="color:#f92672">.</span>dumps(d))</span></span></code></pre></div> ]]></content:encoded>
    </item>
    
    <item>
      <title>German cycle superhighway opens its first stretch</title>
      <link>https://blog.sandipb.net/2016/01/07/german-cycle-superhighway-opens-its-first-stretch/</link>
      <pubDate>Thu, 07 Jan 2016 18:36:10 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2016/01/07/german-cycle-superhighway-opens-its-first-stretch/</guid>
      <description><![CDATA[<figure><a href="https://www.flickr.com/photos/triplefivedrew/907034355/"><img src="/images/2016/01/907034355_167f96f3b5_z.jpg"
    alt="&#39;Commute&#39; by triplefivedrew"></a>
</figure>

<p>The <a href="http://cleantechnica.com/2016/01/04/germany-opens-first-section-of-100km-bicycle-highway/">first 5km of a 100km cycle only superhighway has opened to public</a> in Germany.</p>
<!-- more -->



  <blockquote>
    <p>When complete, the route will connect 10 western cities including Duisburg,  Bochum, and Hamm, and four universities.
Martin Toennes of regional development group RVR says almost two million people live within 1.2 miles of the bicycle
highway and will be able to use sections of it for their daily commutes. With the rise in popularity of electric
bicycles to help with undulating terrain, RVR says the bike way, which utilizes mostly abandoned railroad tracks in the
Ruhr Valley, could replace up to 50,000 motor vehicles during daily commuting hours.</p>]]></description>
      <content:encoded><![CDATA[      <figure><a href="https://www.flickr.com/photos/triplefivedrew/907034355/"><img src="/images/2016/01/907034355_167f96f3b5_z.jpg"
    alt="&#39;Commute&#39; by triplefivedrew"></a>
</figure>

<p>The <a href="http://cleantechnica.com/2016/01/04/germany-opens-first-section-of-100km-bicycle-highway/">first 5km of a 100km cycle only superhighway has opened to public</a> in Germany.</p>
<!-- more -->



  <blockquote>
    <p>When complete, the route will connect 10 western cities including Duisburg,  Bochum, and Hamm, and four universities.
Martin Toennes of regional development group RVR says almost two million people live within 1.2 miles of the bicycle
highway and will be able to use sections of it for their daily commutes. With the rise in popularity of electric
bicycles to help with undulating terrain, RVR says the bike way, which utilizes mostly abandoned railroad tracks in the
Ruhr Valley, could replace up to 50,000 motor vehicles during daily commuting hours.</p>

  </blockquote>

<p>When finished, it would be the longest surfaced urban cycling commute track in the world.</p>
<p>As <a href="http://www.theverge.com/2015/12/29/10684716/germany-bicycle-superhighway-autobahn-infrastructure">Verge</a> reports:</p>



  <blockquote>
    <p>The concept of a bike superhighway is not entirely new, especially in velocipede-friendly European countries. Both
Denmark (22 km between Copenhagen and Albertslund) and Netherlands (7 km between the city of Breda and the town of
Etten-Leur) have their own versions. London is poised to spend €900 million on the East-West Cycle Superhighway, a 30-km
separated bike path connecting Acton in West London with Barking in the east. But if and when it&rsquo;s completed, Germany&rsquo;s
superhighway will certainly stand out as the longest.</p>

  </blockquote>

<p>Clever publicity or not, cycling needs exactly this support from the Government to make it more accessible. Most people
I know shy from cycling specifically for the risk of entangling with motorized transport. This goes a long way to
address that.</p>
<p>Now if US would just get out from the clutches of big oil and started doing something similar. California has some great
cycling trails, and a lot of people do use for commute, but any reasonable commute always forces you to cross traffic at
some point, and statistically <a href="http://www.latimes.com/business/autos/la-fi-hy-californa-leads-">Californian cyclists die more frequently than any other state in the
country</a>.</p>
<p>national-bicycle-deaths-20141027-story.html</p>



  <blockquote>
    <p>If you are going to be killed by a car while riding a bicycle, there’s a good chance you are male, older than 20 and
living in California or Florida.</p>
<p>That’s the finding of a report issued Monday by the Governors Highway Safety Assn. that also noted that between 2010 and
2012, U.S. bicyclist deaths increased by 16%.</p>
<p>California, with 338 cyclists killed in collisions with motor vehicles, and Florida, with 329, had the highest totals
during that period, the report said.</p>
<p>They also had the largest increases in annual cyclist traffic fatalities from 2010 to 2012. Florida’s deaths rose by 37
to 120 in 2012 while cyclist traffic fatalities in California rose by 23 to 123. California had the most bicyclists
killed of any state in 2012.</p>

  </blockquote>

 ]]></content:encoded>
    </item>
    
    <item>
      <title>Using your gut microbes to find the perfect diet</title>
      <link>https://blog.sandipb.net/2015/12/23/using-your-gut-microbes-to-find-the-perfect-diet/</link>
      <pubDate>Wed, 23 Dec 2015 08:54:18 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2015/12/23/using-your-gut-microbes-to-find-the-perfect-diet/</guid>
      <description><![CDATA[<figure><img src="/images/2015/12/15315629849_557ea828d6.jpg"
    alt="Cave Man Paleo breakfast. Photo by Katherine Lim">
</figure>

<div class="img_credit">
Image credit: [Flickr](https://www.flickr.com/photos/ultrakml/15315629849/)
</div>
<p>It seems every generation has its own bouquet of diets that people swear by.</p>
<p>In the early 80s, diet guru <a href="https://en.wikipedia.org/wiki/Nathan_Pritikin">Nathan Pritikin</a> believed that we should shun all fats and food containing cholesterol. He died of leukemia in ’85, but apparently <a href="http://articles.latimes.com/1985-07-04/news/vw-9280_1_nathan-pritikin">his autopsy revealed</a> that he had <em>“arteries like those of a child and a heart like that of a young man”</em>.</p>
<p>His arch rival in the time, <a href="https://en.wikipedia.org/wiki/Robert_Atkins_(nutritionist)">Robert Atkins</a>, of the <a href="https://en.wikipedia.org/wiki/Atkins_diet">Atkin&rsquo;s Diet</a> fame, espoused just the opposite - low-carb, high fat diets. His <a href="http://www.nytimes.com/2004/02/11/nyregion/just-what-killed-the-diet-doctor-and-what-keeps-the-issue-alive.html?_r=0">controversial death</a> threw up allegations of a life long history of cardiac issues and obesity. But still there are people around who swear about it.</p>
<p>Loads of new diets have sprung up in recent years, with a loud number of them blaming carbs, sugar, starches and other GI (glycaemic index) manipulating food groups to be the cause of diet issues in the population.</p>
<p>Now <a href="http://quillette.com/2015/11/27/gi-diets-dont-work-gut-bacteria-and-dark-chocolate-are-a-better-bet-for-losing-weight/">a new article</a> goes a bit deeper. It follows the published &ldquo;study from an Israeli team led by Eran Segal&rdquo;, to suggest that looking at all carbs the same way and avoiding them is too simplistic an approach. Human body is too complex and different sources of carbs affect different people in different ways. One of the major reason that they pointed out was the difference in the profile of the microbes in our digestive system!</p>]]></description>
      <content:encoded><![CDATA[      <figure><img src="/images/2015/12/15315629849_557ea828d6.jpg"
    alt="Cave Man Paleo breakfast. Photo by Katherine Lim">
</figure>

<div class="img_credit">
Image credit: [Flickr](https://www.flickr.com/photos/ultrakml/15315629849/)
</div>
<p>It seems every generation has its own bouquet of diets that people swear by.</p>
<p>In the early 80s, diet guru <a href="https://en.wikipedia.org/wiki/Nathan_Pritikin">Nathan Pritikin</a> believed that we should shun all fats and food containing cholesterol. He died of leukemia in ’85, but apparently <a href="http://articles.latimes.com/1985-07-04/news/vw-9280_1_nathan-pritikin">his autopsy revealed</a> that he had <em>“arteries like those of a child and a heart like that of a young man”</em>.</p>
<p>His arch rival in the time, <a href="https://en.wikipedia.org/wiki/Robert_Atkins_(nutritionist)">Robert Atkins</a>, of the <a href="https://en.wikipedia.org/wiki/Atkins_diet">Atkin&rsquo;s Diet</a> fame, espoused just the opposite - low-carb, high fat diets. His <a href="http://www.nytimes.com/2004/02/11/nyregion/just-what-killed-the-diet-doctor-and-what-keeps-the-issue-alive.html?_r=0">controversial death</a> threw up allegations of a life long history of cardiac issues and obesity. But still there are people around who swear about it.</p>
<p>Loads of new diets have sprung up in recent years, with a loud number of them blaming carbs, sugar, starches and other GI (glycaemic index) manipulating food groups to be the cause of diet issues in the population.</p>
<p>Now <a href="http://quillette.com/2015/11/27/gi-diets-dont-work-gut-bacteria-and-dark-chocolate-are-a-better-bet-for-losing-weight/">a new article</a> goes a bit deeper. It follows the published &ldquo;study from an Israeli team led by Eran Segal&rdquo;, to suggest that looking at all carbs the same way and avoiding them is too simplistic an approach. Human body is too complex and different sources of carbs affect different people in different ways. One of the major reason that they pointed out was the difference in the profile of the microbes in our digestive system!</p>
<p>From their study,</p>
<blockquote>
  People eating the same foods and had the same GI scores had very different glucose responses. Some of this was due to existing weight and age differences in the study’s subjects – but differences remained even in similar people that they couldn’t explain. That was until they looked inside their intestines at the thousands of species of microbes we have inside us.
  ...
  The researchers found that the highly variable microbial profile in the guts of the volunteers determined how quickly the food was broken down and the rate at which glucose appeared in the blood. This was a much stronger effect than the type of carb being eaten. Some people could eat potatoes without any surge in blood sugar and others with the wrong set of microbes just had to look at one and their blood levels peaked.
</blockquote>
<p>Now if you want to find out exactly what your diet should or should not contain, you can sequence your gut profile!</p>
<blockquote>
  Rather than avoiding all foods which might be harmful and reducing your fibre and diversity, profiling your gut microbes from a piece of toilet paper could tell you whether you should be eating potatoes or not. By using the breakthrough techniques of gene sequencing we can now accurately and cheaply identify the different patterns of microbes.
</blockquote>
<p>The article points out an american crowd funded org - <a href="http://americangut.org/">americangut.org</a>, that sounds interesting to me. I should read up on them.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Software patents put on hold in India</title>
      <link>https://blog.sandipb.net/2015/12/22/software-patents-put-on-hold-in-india/</link>
      <pubDate>Tue, 22 Dec 2015 18:13:38 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2015/12/22/software-patents-put-on-hold-in-india/</guid>
      <description><![CDATA[<p>In a welcome move, <a href="http://economictimes.indiatimes.com/articleshow/50275687.cms?utm_source=contentofinterest&amp;utm_medium=text&amp;utm_campaign=cppst">the Indian patent office has temporarily stopped issuing software patents</a>.</p>
<blockquote>
  "In view of several representations received regarding interpretation and scope of section 3(k) of the Patents Act 1970 (as amended), the Guidelines for Examination of Computer Related Inventions... are kept in abeyance till discussions with stakeholders are completed and contentious issues are resolved," the Controller General of Patents said in a notification issued last week.
</blockquote>
<p>Again, this is a temporary measure and given the intensive lobbying that happens behind doors, it could still be revised. But thanks to <a href="http://www.ispirt.in/">Ispirt</a>, <a href="https://www.softwarefreedom.org/">The Software Freedom Law Center</a>, this is being considered seriously by the office.</p>]]></description>
      <content:encoded><![CDATA[      <p>In a welcome move, <a href="http://economictimes.indiatimes.com/articleshow/50275687.cms?utm_source=contentofinterest&amp;utm_medium=text&amp;utm_campaign=cppst">the Indian patent office has temporarily stopped issuing software patents</a>.</p>
<blockquote>
  "In view of several representations received regarding interpretation and scope of section 3(k) of the Patents Act 1970 (as amended), the Guidelines for Examination of Computer Related Inventions... are kept in abeyance till discussions with stakeholders are completed and contentious issues are resolved," the Controller General of Patents said in a notification issued last week.
</blockquote>
<p>Again, this is a temporary measure and given the intensive lobbying that happens behind doors, it could still be revised. But thanks to <a href="http://www.ispirt.in/">Ispirt</a>, <a href="https://www.softwarefreedom.org/">The Software Freedom Law Center</a>, this is being considered seriously by the office.</p>
<p>As the article points out, software patents are ravaging the US software industry. For the big guys, they are used akin to the Mutually Assured Destruction mentality of the cold war, with every company keeping a vast portfolio of patents to fight back with if they are attacked for the same. But there is a big impact on smaller companies too. <a href="http://www.bloomberg.com/bw/articles/2014-06-09/money-for-nothing-how-to-stop-patent-trolls-from-stifling-innovation">Bloomberg reports that</a>:</p>
<blockquote>
  Big companies are not the only ones being clobbered. According to a 2012 study by Boston University Law School professors Michael J. Meurer and James Bessen, some 90 percent of all patent-troll lawsuits are aimed at small and midsize companies. And these companies, when faced with unfathomable potential legal costs, often pay off the trolls just to make them go away.
<p>Overall, Meurer and Bessen found, this abusive system is draining billions of dollars annually from the economy, transferring an estimated $29 billion in 2011 alone from the bank accounts of companies that produce things to patent trolls. Of this, small and midsize businesses, the types that are often at the cutting edge of innovation, paid about 37 percent of the total–money that could have been put to much better use.</p>
</blockquote>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>New HTTP status code for legally unavailable resources</title>
      <link>https://blog.sandipb.net/2015/12/21/new-http-status-code-for-legally-unavailable-resources/</link>
      <pubDate>Mon, 21 Dec 2015 23:19:34 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2015/12/21/new-http-status-code-for-legally-unavailable-resources/</guid>
      <description><![CDATA[<blockquote>The Internet Engineering Task Force(IETF) has finally created a standard for when a page has been taken down due to legal reasons. The new status code, 451, indicates that a host has received a legal demand to deny access to a resource.
</blockquote>
<p><a href="http://thenextweb.com/dd/2015/12/21/theres-now-an-official-http-status-code-for-legal-takedowns-451/">Via TheNextWeb</a></p>]]></description>
      <content:encoded><![CDATA[      <blockquote>The Internet Engineering Task Force(IETF) has finally created a standard for when a page has been taken down due to legal reasons. The new status code, 451, indicates that a host has received a legal demand to deny access to a resource.
</blockquote>
<p><a href="http://thenextweb.com/dd/2015/12/21/theres-now-an-official-http-status-code-for-legal-takedowns-451/">Via TheNextWeb</a></p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Back to Wordpress</title>
      <link>https://blog.sandipb.net/2015/12/21/back-to-wordpress/</link>
      <pubDate>Mon, 21 Dec 2015 10:25:46 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2015/12/21/back-to-wordpress/</guid>
      <description><![CDATA[<p>It seems every year I change my blog backend, hoping it will make a difference to the frequency in blogging. After 10+ years blogging, I am older and wiser enough to know that it doesn&rsquo;t. It is a losing battle. Content I would like to share with my family goes to Facebook, random quips go to Twitter. Pretty much wherever there is a more suitable audience.</p>
<p>In any case, writing or not, it is much better to move to a hosted solution, and I moved my domain and migrated my Jekyll website (painfully) to the <a href="http://wordpress.com">wordpress.com</a> hosted site.</p>]]></description>
      <content:encoded><![CDATA[      <p>It seems every year I change my blog backend, hoping it will make a difference to the frequency in blogging. After 10+ years blogging, I am older and wiser enough to know that it doesn&rsquo;t. It is a losing battle. Content I would like to share with my family goes to Facebook, random quips go to Twitter. Pretty much wherever there is a more suitable audience.</p>
<p>In any case, writing or not, it is much better to move to a hosted solution, and I moved my domain and migrated my Jekyll website (painfully) to the <a href="http://wordpress.com">wordpress.com</a> hosted site.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Serializing structured data into Avro using Python</title>
      <link>https://blog.sandipb.net/2015/05/20/serializing-structured-data-into-avro-using-python/</link>
      <pubDate>Wed, 20 May 2015 02:31:12 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2015/05/20/serializing-structured-data-into-avro-using-python/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/avro-logo.png">
</figure>
<p>It is impossible to ignore avro at work - it is the data serialization format
of choice at work (and rightly so), whether it is to store data into <a href="http://kafka.apache.org/">Kafka</a>
or into our document database <a href="http://data.linkedin.com/projects/espresso">Espresso</a>. Recently, I had the need to read
avro data serialized by a Java application, and I looked into how I might use
Python to read such data.</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/avro-logo.png">
</figure>
<p>It is impossible to ignore avro at work - it is the data serialization format
of choice at work (and rightly so), whether it is to store data into <a href="http://kafka.apache.org/">Kafka</a>
or into our document database <a href="http://data.linkedin.com/projects/espresso">Espresso</a>. Recently, I had the need to read
avro data serialized by a Java application, and I looked into how I might use
Python to read such data.</p>
<p>The <a href="http://avro.apache.org/docs/1.7.6/gettingstartedpython.html">avro python library</a> uses schemas, and can store data in a
<a href="http://avro.apache.org/docs/current/spec.html#binary_encoding">compact binary format</a> using both deflate and <a href="https://code.google.com/p/snappy/">snappy</a>
compression.  I wanted to test out how compact the serialization format is as
compared to say, CSV.</p>
<p>I set up a <a href="http://docs.python-guide.org/en/latest/dev/virtualenvs/">Python virtual environment</a> using the nifty <a href="https://virtualenvwrapper.readthedocs.org/en/latest/">virtualenv
wrapper</a> and installed the python avro library and the snappy
library. On my Ubuntu system, the python snappy library is available from the
package repository, but I used the Pypi one anyway. I had to install the c
development package (<code>libsnappy-dev</code>) as a prerequisite though.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-console" data-lang="console"><span style="display:flex;"><span>$ mkvirtualenv avro
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ workon avro
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>(avro) $ pip install avro
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span># Remember that it is <span style="color:#e6db74">&#39;python-snappy&#39;</span>, and not just <span style="color:#e6db74">&#39;snappy&#39;</span>, which is a
</span></span><span style="display:flex;"><span># completely different library
</span></span><span style="display:flex;"><span>(avro) $ pip install python-snappy
</span></span></code></pre></div>
<p>And I was ready for my code. First I decided on a simple data set: a <a href="https://raw.githubusercontent.com/jvns/pandas-cookbook/master/data/weather_2012.csv">weather
data set</a> which was part of the <a href="http://pandas.pydata.org/pandas-docs/stable/cookbook.html">pandas cookbook</a>.</p>
<p>Based on this, I created this simple schema.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;namespace&#34;</span>: <span style="color:#e6db74">&#34;net.sandipb.avro.example.weather&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;record&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;Reading&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;doc&#34;</span>: <span style="color:#e6db74">&#34;Weather reading at a point in time&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;fields&#34;</span>: [
</span></span><span style="display:flex;"><span>        {<span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;time&#34;</span>, <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;int&#34;</span>, <span style="color:#f92672">&#34;doc&#34;</span>: <span style="color:#e6db74">&#34;Seconds since epoch&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;temp&#34;</span>, <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;float&#34;</span>, <span style="color:#f92672">&#34;doc&#34;</span>: <span style="color:#e6db74">&#34;Temperature in Celsius&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;dew_point_temp&#34;</span>, <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;float&#34;</span>, <span style="color:#f92672">&#34;doc&#34;</span>: <span style="color:#e6db74">&#34;Dew point temperature in Celsius&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;humidity&#34;</span>, <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;int&#34;</span>, <span style="color:#f92672">&#34;doc&#34;</span>: <span style="color:#e6db74">&#34;Relative humidity %&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;wind_speed&#34;</span>, <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;float&#34;</span>, <span style="color:#f92672">&#34;doc&#34;</span>: <span style="color:#e6db74">&#34;Wind speed in km/h&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;visibility&#34;</span>, <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;float&#34;</span>, <span style="color:#f92672">&#34;doc&#34;</span>: <span style="color:#e6db74">&#34;Visibility in km&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;pressure&#34;</span>, <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;float&#34;</span>, <span style="color:#f92672">&#34;doc&#34;</span>: <span style="color:#e6db74">&#34;Atmospheric pressure in kPa&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;weather&#34;</span>, <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, <span style="color:#f92672">&#34;doc&#34;</span>: <span style="color:#e6db74">&#34;Weather summary&#34;</span>}
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Now to actually process the file, I created <a href="https://gist.github.com/sandipb/53ce9e81569adf29d37a#file-csv_to_avro-py">this Python script</a> to read the CSV
file and to write to three different avro files, each with a different
compression format - null (no compression), deflate and snappy.</p>
<p>Here are some relevant parts of the code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Load the avro schema to validate and serialize data</span>
</span></span><span style="display:flex;"><span>schema <span style="color:#f92672">=</span> avro<span style="color:#f92672">.</span>schema<span style="color:#f92672">.</span>parse(open(<span style="color:#e6db74">&#34;weather.avsc&#34;</span>)<span style="color:#f92672">.</span>read())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Open using a avro container class, providing the schema to use and the</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># compression to use</span>
</span></span><span style="display:flex;"><span>writer_deflate <span style="color:#f92672">=</span> DataFileWriter(open(<span style="color:#e6db74">&#34;weather_data_deflate.avro&#34;</span>, <span style="color:#e6db74">&#34;wb&#34;</span>), DatumWriter(), schema, codec<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;deflate&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Write a dict with the data</span>
</span></span><span style="display:flex;"><span>writer_deflate<span style="color:#f92672">.</span>append(row)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># close the container</span>
</span></span><span style="display:flex;"><span>writer_deflate<span style="color:#f92672">.</span>close()</span></span></code></pre></div>
<p>The result of this was &hellip; unexpected. The deflate codec showed more compression
than snappy.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-console" data-lang="console"><span style="display:flex;"><span>$ ls -lSh *.avro *.csv
</span></span><span style="display:flex;"><span>-rw-rw-r-- 1 sandipb sandipb 492K May 20 01:34 weather_2012.csv
</span></span><span style="display:flex;"><span>-rw-rw-r-- 1 sandipb sandipb 317K May 20 02:29 weather_data_null.avro
</span></span><span style="display:flex;"><span>-rw-rw-r-- 1 sandipb sandipb 178K May 20 02:29 weather_data_snappy.avro
</span></span><span style="display:flex;"><span>-rw-rw-r-- 1 sandipb sandipb 121K May 20 02:29 weather_data_deflate.avro
</span></span></code></pre></div>
<p>It is possible that a larger dataset could show better compression for snappy.
This is the first time I have used this codec, I need to read up a bit more
about it. I will try this experiment with a larger dataset and update this post
in the future.</p> ]]></content:encoded>
    </item>
    
    <item>
      <title>Difference between Python and Ruby when it comes to hashes with default values</title>
      <link>https://blog.sandipb.net/2015/05/14/difference-between-python-and-ruby-when-it-comes-to-hashes-with-default-values/</link>
      <pubDate>Thu, 14 May 2015 04:13:44 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2015/05/14/difference-between-python-and-ruby-when-it-comes-to-hashes-with-default-values/</guid>
      <description><![CDATA[<p>Having worked with <a href="https://www.python.org">Python</a> for a while, I am trying to pick up <a href="https://www.ruby-lang.org">Ruby</a>, especially
for some of my work with <a href="http://logstash.net">logstash</a>. While trying out a small program in Ruby, I
got stumped with a peculiar trait of Ruby hashes with default values. It made
me lose an hour of my life I am not going to get back. :(</p>]]></description>
      <content:encoded><![CDATA[      <p>Having worked with <a href="https://www.python.org">Python</a> for a while, I am trying to pick up <a href="https://www.ruby-lang.org">Ruby</a>, especially
for some of my work with <a href="http://logstash.net">logstash</a>. While trying out a small program in Ruby, I
got stumped with a peculiar trait of Ruby hashes with default values. It made
me lose an hour of my life I am not going to get back. :(</p>
<p>In Python, dictionaries with default values is not part of the core language,
and need to be imported from a standard library. For some reason I still don&rsquo;t
know, you cannot set a simple default <em>value</em>, you need supply a <em>function
object</em> which is going to create a value for you. :/</p>
<p>So if I want to create a default dictionary in Python, you need to do something
like this:</p>
<pre tabindex="0"><code class="language-pycon" data-lang="pycon">&gt;&gt;&gt; from collections import defaultdict

&gt;&gt;&gt; d = defaultdict(lambda: 5)</code></pre>
<p>Now when you access any non-existent key in this hash, the hash gets magically
populated with that key and the default value. See below.</p>
<pre tabindex="0"><code class="language-pycon" data-lang="pycon">&gt;&gt;&gt; if d[&#34;one&#34;]: print &#34;not empty&#34;
... 
not empty
&gt;&gt;&gt;
&gt;&gt;&gt; print dict(d)
{&#39;one&#39;: 5}
&gt;&gt;&gt;</code></pre>
<p>Ruby hashes support default values in the core language. So you can do something
like:</p>
<pre tabindex="0"><code class="language-irb" data-lang="irb">&gt;&gt; d = Hash.new(5)
=&gt; {}

&gt;&gt; puts d[&#34;a&#34;]
5
=&gt; nil

&gt;&gt; d
=&gt; {}</code></pre>
<p>Wait! See the difference? Evaluating a non-existing hash position is Ruby
returns the default value, but unlike Python, <strong>it doesn&rsquo;t set the value!</strong></p>
<p>After it drilled down to this quirk, I looked around and found some interesting
articles.</p>
<p><a href="http://dablog.rubypal.com/2008/3/25/a-short-circuit-edge-case">This article</a> from 2008 by David Black was particularly
interesting. He points out in this article how this Hash behaviour breaks a very
popular Ruby idiom.</p>
<p>Normally, the idiomatic Ruby way to initialize or return a variable goes like
this:</p>
<pre tabindex="0"><code class="language-irb" data-lang="irb">&gt;&gt; d = Hash.new
=&gt; {}

&gt;&gt; d[&#34;name&#34;] ||= &#34;Skye&#34;
=&gt; &#34;Skye&#34;

&gt;&gt; d
=&gt; {&#34;name&#34;=&gt;&#34;Skye&#34;}</code></pre>
<p>However, if you use a Hash with a default value, it breaks this idiom because
evaluating a non-existing key returns the default value. However, you would
intuitively expect the <code>||=</code> operator to at least set the missing key to the
default value! But that doesn&rsquo;t happen due to the peculiar treatment to that
operator by Ruby. Ruby evaluates <code>A ||= B </code> NOT to <code>A = A || B</code>, but <code>A || A=B</code>.
This never lets the value to be assigned to keys in default hashes.</p>
<pre tabindex="0"><code class="language-irb" data-lang="irb">&gt;&gt; d = Hash.new(&#34;May&#34;)
=&gt; {}

&gt;&gt; d[&#34;name&#34;] ||= &#34;Skye&#34;
=&gt; &#34;May&#34;

&gt;&gt; d
=&gt; {}</code></pre>
<p>Some gotcha lurking there in an otherwise beautiful language.</p>
<p>Another nice ref:
<a href="http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html">http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html</a></p>
<!-- vim:tw=80:ai --> ]]></content:encoded>
    </item>
    
    <item>
      <title>Pagerduty&#39;s fantastic Zookeeper bug</title>
      <link>https://blog.sandipb.net/2015/05/11/pagerdutys-fantastic-zookeeper-bug/</link>
      <pubDate>Mon, 11 May 2015 23:33:52 +0000</pubDate>
      <author>sandipb (AT) sandipb (DOT) net (Sandip Bhattacharya)</author>
      <guid>https://blog.sandipb.net/2015/05/11/pagerdutys-fantastic-zookeeper-bug/</guid>
      <description><![CDATA[<figure class="alignright"><img src="/images/zookeeper.jpg">
</figure>
<p>Ok, I don&rsquo;t particularly like calling a bug <em>fantastic</em>, in this case, it is
more of a fantastic troubleshooting of a bug. What I found interesting was the
layers that were unpeeled one by one to reach the probable region of the root
cause. (Yeah, the root cause is probably so esoteric and confined to a specific
combination of version, that it is unlikely to be looked at by anybody).</p>]]></description>
      <content:encoded><![CDATA[      <figure class="alignright"><img src="/images/zookeeper.jpg">
</figure>
<p>Ok, I don&rsquo;t particularly like calling a bug <em>fantastic</em>, in this case, it is
more of a fantastic troubleshooting of a bug. What I found interesting was the
layers that were unpeeled one by one to reach the probable region of the root
cause. (Yeah, the root cause is probably so esoteric and confined to a specific
combination of version, that it is unlikely to be looked at by anybody).</p>
<p>Here is Pagerduty&rsquo;s <a href="http://www.pagerduty.com/blog/the-discovery-of-apache-zookeepers-poison-packet/">summary of the bug</a>.</p>



  <blockquote>
    <p>After more than a month of tireless research and testing, we have finally got
to the bottom of our ZooKeeper mystery. Corruption during AES encryption in
Xen v4.1 or v3.4 paravirtual guests running a Linux 3.0+ kernel, combined with
the lack of TCP checksum validation in IPSec Transport mode, which leads to
the admission of corrupted TCP data on a ZooKeeper node, resulting in an
unhandled exception from which ZooKeeper is unable to recover. Jeez. Talk
about a needle in a haystack… Even after all this, we are still unsure where
precisely the bug lies. Despite that fact, we’re still pretty satisfied with
the outcome of the investigation. Now all we need to do is work around it.</p>

  </blockquote> ]]></content:encoded>
    </item>
    
  </channel>
</rss>
