<?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>Config on Haseeb Majid</title>
    <link>https://haseebmajid.dev/tags/config/</link>
    <description>Recent content in Config on Haseeb Majid</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Sun, 19 May 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://haseebmajid.dev/tags/config/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How to Use Env Variables With Viper Config Library in Go</title>
      <link>https://haseebmajid.dev/posts/2024-05-19-how-to-use-env-variables-with-viper-config-library-in-go/</link>
      <pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-05-19-how-to-use-env-variables-with-viper-config-library-in-go/</guid>
      <description>&lt;p&gt;This is part of a series of where I am going to blog about issues I had building my CLI tool OptiNix and documenting
how I resolved those issues. Most will be random things not specifically related to building CLI tools.&lt;/p&gt;
&lt;p&gt;In this example, we will use the &lt;a href=&#34;https://github.com/spf13/viper&#34;&gt;viper&lt;/a&gt; library. Mainly because I am already using cobra, the library to help us make CLI tools.
From the same author and wanted to see how well they integrated. The Viper config library is probably over kill
in my case.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This is part of a series of where I am going to blog about issues I had building my CLI tool OptiNix and documenting
how I resolved those issues. Most will be random things not specifically related to building CLI tools.</p>
<p>In this example, we will use the <a href="https://github.com/spf13/viper">viper</a> library. Mainly because I am already using cobra, the library to help us make CLI tools.
From the same author and wanted to see how well they integrated. The Viper config library is probably over kill
in my case.</p>
<p>In my CLI tool, I wanted the tool to be able to see ENV variables to overwrite certain functionality. This made it much
easier to test my app and also gives more flexibility to the end user.
However, I also wanted to have all of my config in a struct. For example, take a simplified version of the config of
OptiNix.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Sources</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">NixOSURL</span>       <span class="kt">string</span> <span class="s">`mapstructure:&#34;nixos_url&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">HomeManagerURL</span> <span class="kt">string</span> <span class="s">`mapstructure:&#34;home_manager_url&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Config</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">DBFolder</span> <span class="kt">string</span>  <span class="s">`mapstructure:&#34;db_folder&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Sources</span>  <span class="nx">Sources</span> <span class="s">`mapstructure:&#34;sources&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>How can we update this using an environment variable, say we wanted to set the database folder path using an env variable called?
<code>OPTINIX_DB_FOLDER</code>, this code shows how he could do this and then return a struct of type <code>Config</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">LoadConfig</span><span class="p">()</span> <span class="p">(</span><span class="o">*</span><span class="nx">Config</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">config</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">Config</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">viper</span><span class="p">.</span><span class="nf">SetEnvPrefix</span><span class="p">(</span><span class="s">&#34;optinix&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">viper</span><span class="p">.</span><span class="nf">SetEnvKeyReplacer</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReplacer</span><span class="p">(</span><span class="s">`.`</span><span class="p">,</span> <span class="s">`_`</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="nx">viper</span><span class="p">.</span><span class="nf">AutomaticEnv</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">viper</span><span class="p">.</span><span class="nf">ReadInConfig</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">err</span><span class="p">.(</span><span class="nx">viper</span><span class="p">.</span><span class="nx">ConfigFileNotFoundError</span><span class="p">);</span> <span class="p">!</span><span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">viper</span><span class="p">.</span><span class="nf">SetDefault</span><span class="p">(</span><span class="s">&#34;db_folder&#34;</span><span class="p">,</span> <span class="s">&#34;testfolder&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">err</span> <span class="p">=</span> <span class="nx">viper</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;unable to decode into config struct, %v&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We can then access the folder like <code>config.DBFolder</code>. Let&rsquo;s break this code down a bit to understand what is happening.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">SetEnvPrefix</span><span class="p">(</span><span class="s">&#34;optinix&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">SetEnvKeyReplacer</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReplacer</span><span class="p">(</span><span class="s">`.`</span><span class="p">,</span> <span class="s">`_`</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">AutomaticEnv</span><span class="p">()</span>
</span></span></code></pre></div><p>This first part is all around loading in env variables to viper. What we are doing here is first loading env variables
that start with <code>OPTINIX</code>. Using the prefix means we don&rsquo;t have to specify it in each of our config options reducing
boilerplate. However, it also means then our env variables will not conflict with other apps and services.</p>
<p>The next two lines means that we can import environment variables correctly. This is mainly important for the nested
struct we have, i.e. <code>sources</code>. We can set the sources like so <code>OPTINIX_SOURCES_NIXOS_URL</code>. Without the replacement,
it would something like <code>OPTINIX_SOURCES.</code>. The final line loads the env variables into viper.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">viper</span><span class="p">.</span><span class="nf">ReadInConfig</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">err</span><span class="p">.(</span><span class="nx">viper</span><span class="p">.</span><span class="nx">ConfigFileNotFoundError</span><span class="p">);</span> <span class="p">!</span><span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">SetDefault</span><span class="p">(</span><span class="s">&#34;db_folder&#34;</span><span class="p">,</span> <span class="s">&#34;testfolder&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">SetDefault</span><span class="p">(</span><span class="s">&#34;sources.nixos_url&#34;</span><span class="p">,</span> <span class="s">&#34;example.com/nix.hmtl&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Then we load all the config options into Viper. One thing to note is viper can also read config from files as well.
So this part handles that. Then finally, if the database folder is not set via env variable, we give it a default value.
We can see with the nested struct <code>sources</code> we use a <code>.</code>, to specify the field inside that struct.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">err</span> <span class="p">=</span> <span class="nx">viper</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;unable to decode into config struct, %v&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="kc">nil</span>
</span></span></code></pre></div><p>Then in the final part of this function, we unmarshal the viper config into our config struct and return that config.
We can then call this in our <code>main.go</code> and pass the config around to the relevant parts of our code.</p>
<p>Not only does this make our code easier to test, as we can pass in config and set tests to use a local URL, for example.
Maybe a mock server in a Docker container. Then also provides flexibility to our user of the CLI.</p>
<p>That&rsquo;s it! We can now use environment variables to fill in our config struct using the viper config library.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
