Jekyll2020-07-28T10:59:30+00:00http://denis.md/feed.xmlDenis BalanDenis Balan is a cloud advocate, devops engineer, programmer, technologist, writer, microsoft certified, and more.Docker DevOps in Azure Pipelines2020-03-23T12:00:00+00:002020-03-23T12:00:00+00:00http://denis.md/azure-devops-docker-build-deploy<blockquote>
<p>How to develop, push, build, deploy (to Docker Hub) and run docker image-based container in Azure DevOps.</p>
</blockquote>
<p>This article assume that you already are familiar with docker, although it doesn’t focus on Docker we will touch some basics.</p>
<p><img src="/assets/images/azure-devops-docker-build-deploy/shipping-container.jpg" alt="docker container image" title="container image" />
Containerization in real life</p>
<p>The key advantage of Docker is that it allows users to pack the application with all its dependencies into a standardized module for development. Unlike virtual machines, containers do not create such an additional load, so you can use the system and resources more efficiently with them.</p>
<h2 id="why-use-container">Why use container?</h2>
<p>Docker’s takeoff was truly epic. Even though the containers themselves are not a new technology, before Docker they were not so common and popular. Docker changed the situation by providing a standard API that greatly simplified the creation and use of containers and allowed the community to work together on libraries for working with containers.</p>
<h2 id="build-dummy-image">Build dummy image</h2>
<p>Suppose you have a <code class="language-plaintext highlighter-rouge">Dockerfile</code> file that describes your application, if not, let’s create dummy one.
For demo, i will use <code class="language-plaintext highlighter-rouge">nginx</code> image, should be able to see “hello” in <code class="language-plaintext highlighter-rouge">/humans.txt</code> URL</p>
<p><em>Don’t create manually this Dockerfile</em>, in next statement we will create it automatically.</p>
<pre><code class="language-Dockerfile"># Dockerfile
FROM nginx:alpine
COPY humans.txt /usr/share/nginx/html/humans.txt
</code></pre>
<p>To get started, open PowerShell, copy next line, paste, and press enter.</p>
<p>It will create a directory <code class="language-plaintext highlighter-rouge">./demo</code> and inside, two files, first <code class="language-plaintext highlighter-rouge">humans.txt</code> with <code class="language-plaintext highlighter-rouge">hello</code> word inside, and <code class="language-plaintext highlighter-rouge">Dockerfile</code> with content above.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">mkdir</span><span class="w"> </span><span class="o">.</span><span class="nx">/demo</span><span class="p">;</span><span class="w"> </span><span class="nf">cd</span><span class="w"> </span><span class="o">.</span><span class="nx">/demo</span><span class="p">;</span><span class="w"> </span><span class="nf">echo</span><span class="w"> </span><span class="s1">'hello'</span><span class="w"> </span><span class="err">></span><span class="w"> </span><span class="o">.</span><span class="nx">/humans.txt</span><span class="p">;</span><span class="w"> </span><span class="nf">echo</span><span class="w"> </span><span class="s2">"FROM nginx:alpine</span><span class="se">`n</span><span class="s2">COPY humans.txt /usr/share/nginx/html/humans.txt"</span><span class="w"> </span><span class="err">></span><span class="w"> </span><span class="o">.</span><span class="nx">/Dockerfile</span><span class="w">
</span></code></pre></div></div>
<p>To test it locally, let’s build it and run</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># suppose you ran previous statement and Dockerfile is present</span><span class="w">
</span><span class="nf">docker</span><span class="w"> </span><span class="nx">build</span><span class="w"> </span><span class="o">.</span><span class="w"> </span><span class="nt">-t</span><span class="w"> </span><span class="nx">demo</span><span class="w">
</span><span class="nf">docker</span><span class="w"> </span><span class="nx">run</span><span class="w"> </span><span class="nt">-p</span><span class="w"> </span><span class="nx">8080:80</span><span class="w"> </span><span class="nx">demo</span><span class="w">
</span><span class="c"># either open in browser</span><span class="w">
</span><span class="c"># http://localhost:8080/humans.txt</span><span class="w">
</span><span class="c"># or in powershell</span><span class="w">
</span><span class="nf">Invoke-RestMethod</span><span class="w"> </span><span class="nx">http://localhost:8080/</span><span class="w"> </span><span class="o">|%</span><span class="w"> </span><span class="nf">html</span><span class="w"> </span><span class="o">|%</span><span class="w"> </span><span class="nf">head</span><span class="w"> </span><span class="o">|%</span><span class="w"> </span><span class="nf">title</span><span class="w">
</span><span class="nf">Invoke-RestMethod</span><span class="w"> </span><span class="nx">http://localhost:8080/humans.txt</span><span class="w">
</span></code></pre></div></div>
<p><img src="/assets/images/azure-devops-docker-build-deploy/nginx-docker-humans.png" alt="humans.txt file server from nginx docker container" title="nginx alpine docker container" /></p>
<h2 id="push-dockerfile">Push Dockerfile</h2>
<p>Continue with powershell opened, commit and push to Azure DevOps repository.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">git</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="o">.</span><span class="w">
</span><span class="nf">git</span><span class="w"> </span><span class="nx">commit</span><span class="w"> </span><span class="nt">-m</span><span class="w"> </span><span class="s2">"Add Dockerfile"</span><span class="w">
</span><span class="c"># assuming origin points to azure devops</span><span class="w">
</span><span class="nf">git</span><span class="w"> </span><span class="nx">push</span><span class="w"> </span><span class="nt">-u</span><span class="w"> </span><span class="nx">origin</span><span class="w"> </span><span class="nt">--all</span><span class="w">
</span></code></pre></div></div>
<p><img src="/assets/images/azure-devops-docker-build-deploy/git-commit-push-dockerfile.png" alt="commiting and pushing from git" title="powershell command prompt with posh-git" /></p>
<p><em>colored <code class="language-plaintext highlighter-rouge">master</code> word from prompt provided by <a href="https://github.com/dahlbyk/posh-git">posh-git for powershell</a></em></p>
<p>If no errors appeared while pushing, you should be able to see this file structure.</p>
<p><img src="/assets/images/azure-devops-docker-build-deploy/azure-devops-repository.png" alt="azure devops repo" title="file listing in repository" /></p>
<h2 id="add-docker-hub-service-connection">Add Docker Hub service connection</h2>
<p>To add connection to public Docker Hub repository, perform those steps:</p>
<ol>
<li>Click <code class="language-plaintext highlighter-rouge">project settings</code></li>
<li>Under pipeline configuration click <code class="language-plaintext highlighter-rouge">service connections</code></li>
<li>
<p>Click <code class="language-plaintext highlighter-rouge">new service connection</code> on right side</p>
<p><img src="/assets/images/azure-devops-docker-build-deploy/add-dockerhub-azure-devops.png" alt="project settings service connections" title="dockerhub connection" /></p>
</li>
<li>
<p>Specify parameters (your docker id and password)</p>
<p><img src="/assets/images/azure-devops-docker-build-deploy/dockerhub-parameters.png" alt="adding dockerhub to azure devops pipeline" title="dockerhub parameters for azure devops" /></p>
</li>
<li>Click save and verify</li>
</ol>
<p>You are done.</p>
<h2 id="create-yaml-yml-pipeline-file">Create YAML (.yml) pipeline file</h2>
<blockquote>
<p>You could achieve same steps just creating same file with your editor and pushing to repo.</p>
</blockquote>
<p>Now let’s go to Azure DevOps pipelines, click <code class="language-plaintext highlighter-rouge">New pipeline</code>, specify <code class="language-plaintext highlighter-rouge">Azure Repos Git</code> select your repository, and under configure step, select <code class="language-plaintext highlighter-rouge">Starter pipeline</code> like in image below, it should create a minimal <code class="language-plaintext highlighter-rouge">azure-pipelines.yml</code> file, but don’t worry, we will replace it with ours.</p>
<p><img src="/assets/images/azure-devops-docker-build-deploy/starter-pipeline.png" alt="pipelines in azure devops pipeline" title="configuration settings for azure pipeline" /></p>
<p>Paste this into <code class="language-plaintext highlighter-rouge">.yml</code> file.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># build and push image to hub.docker.com</span>
<span class="na">trigger</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">master</span>
<span class="na">resources</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">self</span>
<span class="na">variables</span><span class="pi">:</span>
<span class="na">dockerHub</span><span class="pi">:</span> <span class="s1">'</span><span class="s">mylogin-docker'</span> <span class="c1"># specify your service connection name (create in previos step)</span>
<span class="na">imageName</span><span class="pi">:</span> <span class="s1">'</span><span class="s">mylogin/image-name'</span> <span class="c1"># your desired image name, format: mylogin/image-name</span>
<span class="na">tag</span><span class="pi">:</span> <span class="s1">'</span><span class="s">latest'</span> <span class="c1"># tag, target: mylogin/image-name:latest</span>
<span class="na">stages</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">stage</span><span class="pi">:</span> <span class="s">Build</span>
<span class="na">displayName</span><span class="pi">:</span> <span class="s">Build image</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">job</span><span class="pi">:</span> <span class="s">Build</span>
<span class="na">displayName</span><span class="pi">:</span> <span class="s">Build</span>
<span class="na">pool</span><span class="pi">:</span>
<span class="na">vmImage</span><span class="pi">:</span> <span class="s1">'</span><span class="s">ubuntu-latest'</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">Docker@2</span>
<span class="na">inputs</span><span class="pi">:</span>
<span class="na">containerRegistry</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(dockerHub)'</span>
<span class="na">repository</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(imageName)'</span>
<span class="na">command</span><span class="pi">:</span> <span class="s1">'</span><span class="s">build'</span>
<span class="na">Dockerfile</span><span class="pi">:</span> <span class="s1">'</span><span class="s">**/Dockerfile'</span>
<span class="na">tags</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">$(tag)</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">Docker@2</span>
<span class="na">displayName</span><span class="pi">:</span> <span class="s">Push image</span>
<span class="na">inputs</span><span class="pi">:</span>
<span class="na">containerRegistry</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(dockerHub)'</span>
<span class="na">repository</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(imageName)'</span>
<span class="na">command</span><span class="pi">:</span> <span class="s1">'</span><span class="s">push'</span>
<span class="na">tags</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">$(tag)</span>
</code></pre></div></div>
<p>Now click <code class="language-plaintext highlighter-rouge">Save and run</code></p>
<p><img src="/assets/images/azure-devops-docker-build-deploy/review-pipeline-yaml.png" alt="save and run pipeline" title="pipeline final step" /></p>
<p>Wait until pipeline completes the execution, after that go to your Docker hub account, you should see your container image uploaded.
Or, alternatively, search docker container images from command line</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">docker</span><span class="w"> </span><span class="nx">search</span><span class="w"> </span><span class="nx">mylogin</span><span class="w">
</span><span class="c"># sample output</span><span class="w">
</span><span class="c"># NAME DESCRIPTION STARS OFFICIAL AUTOMATED</span><span class="w">
</span><span class="c"># mylogin/image-name 0</span><span class="w">
</span></code></pre></div></div>
<h2 id="create-release-definition">Create release definition</h2>
<p>After you successfully deployed container image into docker hub from azure devops, to run it you should create a release definition (or alternatively, deploy it via <code class="language-plaintext highlighter-rouge">az cli</code> specifying your image name), in this article i will go with first step.</p>
<p>Navigate to <code class="language-plaintext highlighter-rouge">Pipelines - Releases</code> click <code class="language-plaintext highlighter-rouge">New release pipeline</code></p>
<p><img src="/assets/images/azure-devops-docker-build-deploy/deploy-docker-hub-to-appservice.png" alt="release pipeline creation" title="deploy to app service from public docker hub images" /></p>
<p>You can start with <code class="language-plaintext highlighter-rouge">Empty job</code> predefined template, after that navigate to tasks, under <code class="language-plaintext highlighter-rouge">Run on agent</code> click add button, select <code class="language-plaintext highlighter-rouge">Azure App Service deploy</code> and specify parameters:</p>
<ol>
<li>Your Subscription</li>
<li>App Service type - Web App for Containers (Linux)</li>
<li>App Service name (select or go to portal and create new container-provided app service)</li>
<li>Registry or namespace - docker.io</li>
<li>Image - <code class="language-plaintext highlighter-rouge">mylogin/image-name</code> specified in build step</li>
<li>Click Save and Create release</li>
</ol>
<p>After all steps done, navigate to your app service in browser, appending <code class="language-plaintext highlighter-rouge">/humans.txt</code> at the end of the URL.
<img src="/assets/images/azure-devops-docker-build-deploy/humans-txt-docker.png" alt="result of deployed container image" title="humans file served from azure app service" /></p>How to develop, push, build, deploy (to Docker Hub) and run docker image-based container in Azure DevOps.COVID-19 impact on working from home2020-03-18T12:00:00+00:002020-03-18T12:00:00+00:00http://denis.md/how-to-work-efficiently-from-home<p>Modern problems, modern solutions, COVID-19 it surprised us with his appearance, in order to manage the impacts of corona-virus, remote work seems to be good for everyone: comfortable conditions, cozy atmosphere, free schedule. But these benefits can be a hindrance. How to learn to work productively from home?</p>
<p><img src="/assets/images/photo-of-man-wearing-gray-shirt-and-apron.jpg" alt="man working from home" title="Work from home" />
<a href="https://www.pexels.com/photo/photo-of-man-wearing-gray-shirt-and-apron-374079/">photo credit</a></p>
<p>Working from home has many advantages: you do not waste time on the way to the office, you make up your own schedule of the working day, you may have more time for yourself.</p>
<p>On the other hand, it can be difficult to achieve the same level of productivity as in the office. You can easily give in to laziness, distracted by trifles, stretch small tasks for the whole day. To make sure this doesn’t happen, i am sharing my experience and tips to help you stay focused and work effectively.</p>
<h2 id="set-up-your-own-home-office">Set up your own home office</h2>
<p>I set up aside a corner in the apartment for workplace. There is no TV, no household items, no children’s toys.</p>
<p>You should distinguish between the place where you sleep and rest and the area where you work. Other life-hack that you can try is to buy a screen and fence off your desk during the working day.</p>
<blockquote>
<p>BBC article on <a href="https://www.bbc.com/worklife/article/20200312-coronavirus-covid-19-update-work-from-home-in-a-pandemic" title="BBC article from Bryan Lufkin">working remotely during COVID-19 crisis</a></p>
</blockquote>
<h2 id="keep-to-the-schedule">Keep to the schedule</h2>
<p>Who works from home is his own boss, so it is very easy to succumb to the temptation and finish work a couple of hours early, or a couple of hours later, or stretch your lunch.</p>
<p>Make sure you have a clear time schedule and try to keep it. Smartphone reminders can help you do this by letting you know it’s time to finish your snack or finish today’s work.</p>
<h2 id="rest-for-the-eyes">Rest for the eyes</h2>
<p>Working from home often means that you have far fewer face-to-face meetings that could help eyes rest from the computer screen. Trying to do quality work with tired eyes or exhausted minds will not lead to the desired result.</p>
<p><img src="/assets/images/eye-exercises.jpg" alt="eye exercises" /></p>
<p><a href="https://www.icarevision.com/vision-therapy/eye-exercises-you-can-try-at-home/">photo credit</a></p>
<p>Time will be wasted. But if there are only a few small tasks, then it is better to take a short break and productively complete all of them.</p>
<h2 id="take-breaks">Take breaks</h2>
<p>One of the downsides of the home office - with all the possible distractions, there is no possibility at home to be distracted for a few minutes and take a break.</p>
<p>When in the office, i can do this while going the water cooler, or chatting over a cup of coffee. Try to take short breaks though - go out for a breath on the balcony, take your dog (if you have one) for a walk and run for half an hour.</p>
<h2 id="socialize">Socialize</h2>
<p>Although modern technology offers many opportunities for communication, do not forget about live communication. Arrange personal meetings with colleagues, friends or work partners at least several times a week (not while in quarantine of <em>COVID-19</em> of course).</p>
<h2 id="bonus-twitter-whftips">Bonus: Twitter #WHFTips</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> https://twitter.com/natsmillie/status/1240253294523580417
</code></pre></div></div>
<p>I hope these advices will help you overcome sudden laziness and you will work very productively.</p>Modern problems, modern solutions, COVID-19 it surprised us with his appearance, in order to manage the impacts of corona-virus, remote work seems to be good for everyone: comfortable conditions, cozy atmosphere, free schedule. But these benefits can be a hindrance. How to learn to work productively from home?Powershell Module: unrevealing process of developing, testing and publishing2020-03-11T22:00:00+00:002020-03-11T22:00:00+00:00http://denis.md/create-powershell-module<blockquote>
<p><em>tl;dr</em> How to develop and publish basic PowerShell 7 module with PSScriptAnalyzer, Pester, Plaster, PSDeploy, BuildHelpers, InvokeBuild invoking CI build on AppVeyor that test the module to not break anything, and CD pipeline to publish to PowershellGallery.</p>
</blockquote>
<p>Daily experience with PowerShell implies itself dealing with scripts, being it plain scripts, grouped cmdlets or whole modules. Properly packaging your code into a reusable module is one way of achieving <a href="https://en.wikipedia.org/wiki/Separation_of_concerns">separation of concerns</a>.</p>
<blockquote>
<p>Module that extracts information from <a href="http://bnm.md/en">National Bank of Moldova</a> (Banca Națională a Moldovei - BNM) about today’s exchange rate for foreign currencies for integrating into application backends, excel sheets, and more.</p>
</blockquote>
<h2 id="what-is-a-powershell-module">What is a PowerShell module</h2>
<p>A module is a set of files containing scripts, resources, dependent assemblies that you want to include in the project. As docs from <a href="https://docs.microsoft.com/en-us/powershell/scripting/developer/module/understanding-a-windows-powershell-module">msdn</a> says, there are four different module types each one suitable for certain needs:</p>
<h3 id="script-modules">Script Modules</h3>
<p>A file with .psm1 extension that contain valid PowerShell code, think of this as packaging your set of utilities, functions, workflows, and other.</p>
<h3 id="binary-modules">Binary Modules</h3>
<p>Assembly that contains compiled code, this type of module that operates at a lower level in comparison to script module, allowing developers to take advantage of full .NET framework features (ex TPL).</p>
<h3 id="manifest-modules">Manifest Modules</h3>
<p>Contains meta-information about the module, such as author, requirements, function exports. In this type of module, you can also use the features, such as the ability to load up dependent assemblies or automatically run certain pre-processing scripts.</p>
<h3 id="dynamic-modules">Dynamic Modules</h3>
<p>Modules that were created in runtime by <code class="language-plaintext highlighter-rouge">New-Module</code> cmdlet, cannot be accessed by <code class="language-plaintext highlighter-rouge">Get-Module</code>, they do not need manifests, and most likely no permanent folder to store themselves.</p>
<p>For this project i choose script module.</p>
<h2 id="know-your-tools">Know your tools</h2>
<p>Building a module nowadays without additional tools, ie “from scratch” is a messy task, below are several tools that helps automate steps.</p>
<table>
<thead>
<tr>
<th>Tool</th>
<th style="text-align: center">Used for</th>
<th style="text-align: right">Stage</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/PowerShell/Plaster">Plaster</a></td>
<td style="text-align: center">module templating</td>
<td style="text-align: right">develop</td>
</tr>
<tr>
<td><a href="https://github.com/RamblingCookieMonster/BuildHelpers">BuildHelpers</a></td>
<td style="text-align: center">dependency restorer</td>
<td style="text-align: right">build</td>
</tr>
<tr>
<td><a href="https://github.com/nightroman/Invoke-Build">InvokeBuild</a></td>
<td style="text-align: center">build automation</td>
<td style="text-align: right">build</td>
</tr>
<tr>
<td><a href="https://github.com/PowerShell/PSScriptAnalyzer">PSScriptAnalyzer</a></td>
<td style="text-align: center">static code checker</td>
<td style="text-align: right">testing</td>
</tr>
<tr>
<td><a href="https://github.com/pester/Pester">Pester</a></td>
<td style="text-align: center">testing and mocking</td>
<td style="text-align: right">testing</td>
</tr>
<tr>
<td><a href="https://github.com/PowerShell/Polaris">Polaris</a></td>
<td style="text-align: center">HTTP framework for PowerShell</td>
<td style="text-align: right">testing</td>
</tr>
<tr>
<td><a href="https://github.com/RamblingCookieMonster/PSDeploy">PSDeploy</a></td>
<td style="text-align: center">deployment to AppVeyor</td>
<td style="text-align: right">deploy</td>
</tr>
</tbody>
</table>
<h2 id="module-description">Module description</h2>
<p>To install the module open <code class="language-plaintext highlighter-rouge">pwsh</code> console and type following</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">Install-Module</span><span class="w"> </span><span class="nx">BNMoldovaCurrency</span><span class="w"> </span><span class="nt">-scope</span><span class="w"> </span><span class="nx">CurrentUser</span><span class="p">;</span><span class="w">
</span><span class="nf">Import-Module</span><span class="w"> </span><span class="nx">BNMoldovaCurrency</span><span class="w">
</span></code></pre></div></div>
<p>Below is a table explaining each file’s responsibility</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">Get-Command</span><span class="w"> </span><span class="nt">-Module</span><span class="w"> </span><span class="nx">BNMoldovaCurrency</span><span class="w"> </span><span class="o">|%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nf">Get-Help</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">select</span><span class="w"> </span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="nx">synopsis</span><span class="p">,</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'description'</span><span class="err">;</span><span class="w"> </span><span class="nx">e</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">{</span><span class="w"> </span><span class="bp">$_</span><span class="err">.</span><span class="nx">description</span><span class="err">[</span><span class="nx">0</span><span class="err">].</span><span class="nx">Text</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Synopsis</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Get-BNMConfig</td>
<td>Get the default configuration for BNM.</td>
<td>Get the default configuration for Banca Nationala of Moldova.</td>
</tr>
<tr>
<td>Get-BNMCurrency</td>
<td>Gets BNM currency for specified date.</td>
<td>Invokes HTTP GET method to the BNM server for reading exchange rates based on configuration file.</td>
</tr>
<tr>
<td>Save-BNMCurrency</td>
<td>Saves BNM currency for specified date.</td>
<td>Uses Get-BNMCurrency to get data.</td>
</tr>
<tr>
<td>Set-BNMConfig</td>
<td>Set the default configuration for Banca Nationala of Moldova.</td>
<td>Set the default configuration for BNM server.</td>
</tr>
</tbody>
</table>
<p>Manifest file contains following basic properties</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Author of this module</span><span class="w">
</span><span class="nf">Author</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Denis Balan'</span><span class="w">
</span><span class="c"># Company or vendor of this module</span><span class="w">
</span><span class="nf">CompanyName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Unknown'</span><span class="w">
</span><span class="c"># Copyright statement for this module</span><span class="w">
</span><span class="nf">Copyright</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'(c) 2020 Denis Balan. All rights reserved.'</span><span class="w">
</span><span class="c"># Description of the functionality provided by this module</span><span class="w">
</span><span class="nf">Description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Module for interacting with BNM exchange rates.'</span><span class="w">
</span><span class="c"># Minimum version of the Windows PowerShell engine required by this module</span><span class="w">
</span><span class="nf">PowerShellVersion</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'5.1'</span><span class="w">
</span></code></pre></div></div>
<h2 id="testing">Testing</h2>
<p>There are several tests in <code class="language-plaintext highlighter-rouge">tests</code> folder:</p>
<ul>
<li><a href="https://github.com/DenisBalan/BNMoldovaCurrency/blob/master/tests/1.BNMoldovaCurrency.Tests.ps1">1.BNMoldovaCurrency.Tests.ps1</a> - tests file integrity with PSScriptAnalyzer</li>
<li><a href="https://github.com/DenisBalan/BNMoldovaCurrency/blob/master/tests/2.Help.Tests.ps1">2.Help.Tests.ps1</a> - requires functions to have examples, description and synopsis</li>
<li><a href="https://github.com/DenisBalan/BNMoldovaCurrency/blob/master/tests/Get-BNMConfig.Tests.ps1">Get-BNMConfig.Tests.ps1</a> - tests <code class="language-plaintext highlighter-rouge">Set-BNMConfig</code> and <code class="language-plaintext highlighter-rouge">Get-BNMConfig</code> to return proper endpoint</li>
<li><a href="https://github.com/DenisBalan/BNMoldovaCurrency/blob/master/tests/Get-BNMCurrency.Tests.ps1">Get-BNMCurrency.Tests.ps1</a> - tests xml parsing of exchange rates to have predefined schema (<code class="language-plaintext highlighter-rouge">Polaris</code> used as a proxy for mocking BNM server)</li>
</ul>
<h2 id="summary">Summary</h2>
<p>To build it locally</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">git</span><span class="w"> </span><span class="nx">clone</span><span class="w"> </span><span class="nx">https://github.com/DenisBalan/BNMoldovaCurrency</span><span class="w">
</span><span class="nf">cd</span><span class="w"> </span><span class="nx">BNMoldovaCurrency</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="o">.</span><span class="nx">/build.ps1</span><span class="w">
</span></code></pre></div></div>
<p><img src="/assets/images/building-bnmoldovacurrency-pwsh-module.gif" alt="building powershell module" /></p>
<h3 id="use-cases">Use cases</h3>
<h4 id="generate-csv-of-eur-for-last-several-days">Generate CSV of EUR for last several days</h4>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">$</span><span class="p">(</span><span class="mi">100</span><span class="o">..</span><span class="mi">0</span><span class="w"> </span><span class="o">|%</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nv">$date</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="si">$(</span><span class="s1">'{0:dd.MM.yyyy}'</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="err">$</span><span class="p">(</span><span class="nf">get-date</span><span class="p">)</span><span class="o">.</span><span class="nf">AddDays</span><span class="p">(</span><span class="nf">-</span><span class="bp">$_</span><span class="p">)</span><span class="si">)</span><span class="s2">"</span><span class="w">
</span><span class="nf">Set-BNMConfig</span><span class="w"> </span><span class="nt">-Endpoint</span><span class="w"> </span><span class="nx">https://bnm.md/ro/official_exchange_rates</span><span class="w"> </span><span class="nt">-Params</span><span class="w"> </span><span class="p">@{</span><span class="nx">get_xml</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">1</span><span class="err">;</span><span class="w"> </span><span class="nx">date</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$date</span><span class="w"> </span><span class="p">};</span><span class="w">
</span><span class="p">[</span><span class="kt">pscustomobject</span><span class="p">]@{</span><span class="nx">date</span><span class="o">=</span><span class="nv">$date</span><span class="err">;</span><span class="w"> </span><span class="nx">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">[</span><span class="nx">float</span><span class="err">]$(</span><span class="nx">Get</span><span class="err">-</span><span class="nx">BNMCurrency</span><span class="w"> </span><span class="err">|?</span><span class="w"> </span><span class="err">{</span><span class="w"> </span><span class="bp">$_</span><span class="err">.</span><span class="nx">charcode</span><span class="w"> </span><span class="err">-</span><span class="nx">match</span><span class="w"> </span><span class="s1">'eur'</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|%</span><span class="w"> </span><span class="nf">value</span><span class="p">)}</span><span class="w">
</span><span class="p">});</span><span class="w">
</span></code></pre></div></div>
<p>Having <code class="language-plaintext highlighter-rouge">$data</code> in memory, we can export it to CSV</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">
</span><span class="nv">$data</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">Export-Csv</span><span class="w"> </span><span class="o">.</span><span class="nx">/rata_eur_mdl.csv</span><span class="w"> </span><span class="nt">-NoTypeInformation</span><span class="w">
</span></code></pre></div></div>
<p>Example</p>
<table>
<thead>
<tr>
<th>date</th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr>
<td>09.03.2020</td>
<td>19.7466</td>
</tr>
<tr>
<td>10.03.2020</td>
<td>19.8699</td>
</tr>
<tr>
<td>11.03.2020</td>
<td>19.7153</td>
</tr>
<tr>
<td>12.03.2020</td>
<td>19.7277</td>
</tr>
<tr>
<td>…</td>
<td> </td>
</tr>
</tbody>
</table>
<p>Or even plot it in the terminal</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># this will install Graphical module</span><span class="w">
</span><span class="nf">Install-Module</span><span class="w"> </span><span class="nx">Graphical</span><span class="w"> </span><span class="nt">-scope</span><span class="w"> </span><span class="nx">CurrentUser</span><span class="p">;</span><span class="w">
</span><span class="nf">Import-Module</span><span class="w"> </span><span class="nx">Graphical</span><span class="w">
</span><span class="nv">$days100</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$data</span><span class="w"> </span><span class="o">|%</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">value</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">100</span><span class="p">;</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="nf">Show-Graph</span><span class="w"> </span><span class="nt">-Datapoints</span><span class="w"> </span><span class="nv">$days100</span><span class="w"> </span><span class="nt">-GraphTitle</span><span class="w"> </span><span class="s1">'100 EUR -> MDL'</span><span class="w">
</span></code></pre></div></div>
<p>You should see something like this
<img src="/assets/images/eur-to-mdl-rate-graph.png" alt="100 EUR to MDL in time" /></p>
<p>Writing PowerShell module is not a thing you should scary, but instead enjoy all steps.</p>
<p>Feel free to fork it via GitHub - <a href="https://github.com/DenisBalan/BNMoldovaCurrency">BNMoldovaCurrency</a>.</p>tl;dr How to develop and publish basic PowerShell 7 module with PSScriptAnalyzer, Pester, Plaster, PSDeploy, BuildHelpers, InvokeBuild invoking CI build on AppVeyor that test the module to not break anything, and CD pipeline to publish to PowershellGallery.