Engineer taking Dev?Ops notes https://dexterposh.github.io/ Recent content on Engineer taking Dev?Ops notes Hugo -- gohugo.io en-us Fri, 15 Oct 2021 12:23:30 +0530 About https://dexterposh.github.io/about/ Fri, 03 Jul 2020 00:00:00 +0000 https://dexterposh.github.io/about/ <p>My name is <code>Deepak</code> and I currently live in Bengaluru working as a systems engineer. In my own time and at work, I do some of the below:</p> <ul> <li>Write code <code>[C#, PowerShell, Python]</code>.</li> <li>Work on Cloud <code>[Azure]</code>.</li> <li>Work on Application Build/Release/Deploy.</li> </ul> <p>I am trying to learn how to code</p> Subscribe https://dexterposh.github.io/subscribe/ Fri, 03 Jul 2020 00:00:00 +0000 https://dexterposh.github.io/subscribe/ Kubernetes API Terms https://dexterposh.github.io/posts/012-k8s-api-terms/ Fri, 15 Oct 2021 12:23:30 +0530 https://dexterposh.github.io/posts/012-k8s-api-terms/ <h2 id="overview">Overview</h2> <p>My notes on the Kubernetes API terminology.</p> <h2 id="api-server">API-Server</h2> <p>The K8s core component api-server is at the heart of the interactions made by Client and other K8s components. It is the only component that can interact with the the etcd (key-value) store.</p> <p>Reponsibilities:</p> <ul> <li>Serve the RESTful HTTP API <ul> <li>Reading state - fetch single object, list of objects and stream changes (via events)</li> <li>Modify state - Create, Update &amp; Delete objects</li> </ul> </li> <li>Proxy cluster components e.g. Dashboard, kubectl exec sessions etc.</li> </ul> <p>API server HTTP interface handles HTTP requests to query and manipulate K8s resources using the standard HTTP verbs:</p> <ul> <li>HTTP GET - retrieve data with a specific resource or collection or list of resources</li> <li>HTTP POST - create a resource</li> <li>HTTP PUT - Updating an existing resource</li> <li>HTTP PATCH - Partial updates to resources</li> <li>HTTP DELETE - Destroy a resource (non-recoverable manner)</li> </ul> <p>Above means that when we use a client like Kubectl to interact with the API-Server it is actually issuing HTTP requests against a certain endpoint.</p> <h3 id="api-terminology">API terminology</h3> <p>Below are some of the most common terms thrown around when referring to the REST HTTP API.</p> <h4 id="kind">Kind</h4> <p>To simply put this is the data type of the underlying object.</p> <p>Broadly there are 3 categories of types:</p> <ul> <li>Objects - this category represents individual object types like pod, deployment etc.</li> <li>List - this data type is basically a collection of objects defined above.</li> <li>Special kind of types - this collections refers to types which define action on objects or non-persistent entities.</li> </ul> <p>Note - In K8s the <code>kind</code> directly corresponds with a Golang type.</p> <h4 id="api-group">API Group</h4> <p>When logically related kind(s) are grouped together it is referred to as API group. For example - Kind <code>Job</code> or <code>ScheduledJob</code> are placed under the Batch API group.</p> <h4 id="version">Version</h4> <p>Each API group can exist in multiple version e.g. an API group first appears as v1alpha1 and is then promoted to v1beta1 and finally graduates to v1.</p> <p>K8s supports multiple API versions for making it easy to extend, these are served under different API paths e.g. /api/v1 or /apis/extensions/v1beta1.</p> <p>Different API verions imply diff level of stability and support:</p> <ul> <li>Alpha level (e.g. v1alpha1) - disabled by default, support for a feature may be dropped at any time, use only in short lived testing clusters.</li> <li>Beta level (e.g. v2beta3) enabled by default, code well tested. However object semantics might get a breaking change in next release.</li> <li>Stable (e.g. v1) will appear in software for many subsequent versions.</li> </ul> <h4 id="resource">Resource</h4> <p>This is usually a plural word and uniquely identifies the HTTP endpoint exposing CRUD semantics for an object type e.g. /pods, /pods/busybox. Common path patterns are:</p> <ul> <li>The root e.g. /pods which lists all the instances of that type</li> <li>Individual named resources e.g /pods/busybox</li> </ul> <blockquote> <p>Resources correspond to HTTP endpoint paths</p> </blockquote> <blockquote> <p>Kinds are the type of objects returned and recieved by these endpoints, which are then persisted into the datastore.</p> </blockquote> <h3 id="points-to-note">Points to note</h3> <ul> <li>Resources are always part of an API group and version, also referred to as GroupVersionResource (GVR)</li> <li>GVR basically uniquely defines an HTTP endpoint path</li> <li>Each kind also is part of an API group and versio, referred to as GroupVersionKind (GVK)</li> <li>Relation between GVK &amp; GVR is - GVKs are served under the HTTP Path identified by GVR.</li> <li>Above process of mapping GVK to GVR is also termed as REST Mapping in the K8s SDK.</li> </ul> Azure Vms Resource Graph Queries https://dexterposh.github.io/posts/013-az-vms-resource-graph/ Mon, 24 May 2021 10:47:38 +0530 https://dexterposh.github.io/posts/013-az-vms-resource-graph/ <h2 id="why-use-resource-graph-instead-az-cli-powershell">Why use Resource Graph instead Az CLI/ PowerShell?</h2> <p>If we want to search for resources meeting certain criteria across all our subscriptions, we can&rsquo;t use Az CLI or Az PowerShell to do this type of queries since it would require a lot of overhead to filter and switch between subscription contexts.</p> <p>Pesudo-Code (not so performant)</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-txt" data-lang="txt">Foreach subscription in subscriptions: set Az Context Get the VMs, Filter based on criteria </code></pre></div><p>Resource Graph queries can help here, as they can be used to fetch metadata about the resource and then after their presence is validated, maybe perform some operation on it. With PowerShell we can even group these resources together based on SubscriptionID and then iterate over each subscription (set the right context) and perform actions.</p> <p>Pesudo-Code (single query and performant)</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-txt" data-lang="txt">Query Azure Graph for resources based on criteria across all the subscriptions </code></pre></div><p>In this post, I share some of the Resource Graph Queries I have found useful while working with Virtual Machines.</p> <h2 id="prerequisite">Prerequisite</h2> <p>Prerequisite is the Az CLI (with graph extension) or Az.ResourceGraph PowerShell module which supports 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-powershell" data-lang="powershell"><span style="color:#75715e"># Install the Resource Graph module from PowerShell Gallery</span> Install-Module -Name Az.ResourceGraph </code></pre></div><p>For Az CLI run the below to install the extension:</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="color:#75715e"># Add the Resource Graph extension to the Azure CLI environment</span> az extension add --name resource-graph </code></pre></div><h2 id="virtual-machine-queries">Virtual Machine Queries</h2> <p>Below are list of queries for Virtual machines.</p> <h3 id="find-virtual-machine-with-name">Find Virtual Machine with Name</h3> <p>If you want to find out if a virtual machine is present across all the subscriptions you have access to, you can use the below Resource graph query with Az CLI.</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-powershell" data-lang="powershell"><span style="color:#75715e"># Using Az.ResourceGraph Module</span> Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; and name == &#39;testvm01&#39;&#34;</span> </code></pre></div><p>Now for most of the queries in this post, you can do one to one translation to Az CLI commands by extracting the query and using it, I prefer to use PowerShell and that will be used in the rest of 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" data-lang="shell"><span style="color:#75715e"># Using AZ CLI</span> az graph query -q <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; and Name == &#39;testvm01&#39;&#34;</span> </code></pre></div><h3 id="find-virtual-machines-with-name-regex-matching">Find Virtual machines with name regex matching</h3> <p>Note - This regex matching can be applied to any resource or any property defined in the Resource schema 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-powershell" data-lang="powershell">Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; and name matches regex &#39;test-[0-9].*&#39;&#34;</span> </code></pre></div><h3 id="find-virtual-machines-with-a-specific-tag">Find Virtual machines with a specific tag</h3> <p>Gist is Kusto allows for filtering at all the levels, so we can filter based on a specific tag or can chain multiple tags to filter.</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-powershell" data-lang="powershell"><span style="color:#75715e"># Filter based on a tag called the department</span> <span style="color:#75715e"># Note that tags are case-sensitive</span> Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; and tags.department =~ &#39;ITDepartment&#39;&#34;</span> <span style="color:#75715e"># Filter based on the groupEmail name</span> Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; | where tags.group =~ &#39;SRE&#39;&#34;</span> <span style="color:#75715e"># Combine both the department &amp; group</span> Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; | where tags.group =~ &#39;SRE&#39; or tags.department =~ &#39;ITDepartment&#39;&#34;</span> </code></pre></div><h3 id="find-virtual-machines-deployed-using-marketplace-images">Find Virtual machines deployed using MarketPlace images</h3> <p>This query utilizes the fact that the publisher field for a market place will not be empty.</p> <p>Note - Remove the end &lsquo;limit&rsquo; command expression at the end of query if you need the exhaustive list.</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-powershell" data-lang="powershell">Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; and isnotempty(properties.storageProfile.imageReference.publisher)| limit 1&#34;</span> </code></pre></div><h3 id="find-virtual-machines-deployed-using-custom-images">Find Virtual machines deployed using custom Images</h3> <p>This query utilizes the fact that the publisher field does not exist for a VM deployed using a generalized image.</p> <p><strong>Note</strong> - Remove the end &lsquo;limit&rsquo; command expression at the end of query if you need the exhaustive list.</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-powershell" data-lang="powershell">Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; and isempty(properties.storageProfile.imageReference.publisher)| limit 1&#34;</span> </code></pre></div><h3 id="find-virtual-machines-which-do-not-have-a-tag-populated">Find Virtual machines which do not have a Tag populated</h3> <p>In this example the query checks for VMs which do not have a specific tag named Department created but not populated.</p> <p>Note - Remove the end &lsquo;limit&rsquo; command expression at the end of query if you need the exhaustive list.</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-powershell" data-lang="powershell">Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; and isempty(tags.group) </span><span style="color:#e6db74">| project name, location, resourceGroup, subscriptionId, Group=tags.group </span><span style="color:#e6db74">| limit 5&#34;</span> </code></pre></div><h3 id="find-virtual-machine-with-an-ipaddress">Find Virtual machine with an IPAddress</h3> <p>This can simply be done by doing a reverse lookup of the IPaddress but in some cases where the machines did not have these DNS records created it was a pain.</p> <p>We start by looking for network interfaces which have this IP address</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-powershell" data-lang="powershell">$nic = Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Network/networkInterfaces&#39; | </span><span style="color:#e6db74">where properties.ipConfigurations[0].properties.privateIPAddress == &#39;10.10.10.6&#39;&#34;</span> $nic.properties.virtualMachine.id <span style="color:#75715e"># this is the resource ID of the VM</span> <span style="color:#75715e"># Now we can fire off another query to get the VM info</span> Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; and id == </span><span style="color:#ae81ff">`&#39;</span>$($nic.properties.virtualMachine.id)<span style="color:#ae81ff">`&#39;</span><span style="color:#e6db74">&#34;</span> </code></pre></div><h3 id="gather-extra-virtual-machine-information">Gather extra Virtual Machine information</h3> <p>To gather the relevant information for different VM resources e.g. network, storage, subscription etc, one can use PowerShell to extend the object returned from the resource graph query.</p> <p>Below I show usage of a crude way of using PowerShell to do this e.g. using a filter.</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-powershell" data-lang="powershell"><span style="color:#66d9ef">Filter</span> GetVMInfo { $networkQuery = <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Network/networkInterfaces&#39; and id == </span><span style="color:#ae81ff">`&#39;</span>$($PSItem.properties.networkProfile.networkInterfaces[0].id)<span style="color:#ae81ff">`&#39;</span><span style="color:#e6db74">&#34;</span> $network = Search-AzGraph -Query $networkQuery <span style="color:#66d9ef">[pscustomObject]</span>@{ Name = $PSItem.name Location = $PSItem.location Tags = $PSItem.Tags ResourceGroup = $PSItem.resourceGroup SubscriptionID = $PSItem.subscriptionId SubscriptionName = (Get-AzSubscription -SubscriptionId $PSItem.subscriptionId).Name ProvisioningState = $PSItem.properties.provisioningState VMSize = $PSItem.properties.hardwareProfile.vmSize BaseOSImage = $PSItem.properties.storageProfile.imageReference.id.Split(<span style="color:#e6db74">&#39;/&#39;</span>)[-1] OSType = $PSItem.properties.storageProfile.osDisk.osType OSDiskSize = $PSItem.properties.storageProfile.osDisk.diskSizeGB DataDisks = <span style="color:#66d9ef">foreach</span> ($disk <span style="color:#66d9ef">in</span> $PSItem.properties.storageProfile.dataDisks) { <span style="color:#e6db74">&#34;Name = {0}, Size = {1}, IsManaged = </span>$(<span style="color:#66d9ef">if</span> ($disk.managedDisk) { $true} <span style="color:#66d9ef">else</span> {$false})<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">-f</span> $disk.name, $disk.diskSizeGB } IPAddress = $network.properties.ipConfigurations[0].properties.privateIPAddress SubnetName = $network.properties.ipConfigurations[0].properties.subnet.id.Split(<span style="color:#e6db74">&#39;/&#39;</span>)[-1] DNSservers = @($network.properties.dnsSettings.dnsServers) } } </code></pre></div><p>Use the above filter like below:</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-powershell" data-lang="powershell"><span style="color:#75715e"># Search for the VM first using the Az Resurce Graph</span> $VM = Search-AzGraph -Query <span style="color:#e6db74">&#34;where type =~ &#39;Microsoft.Compute/VirtualMachines&#39; and name == &#39;testvm01&#39;&#34;</span> <span style="color:#75715e"># Pipe the object(s) found to the filter we defined</span> $VM | GetVMInfo </code></pre></div><h2 id="conclusion">Conclusion</h2> <p>Azure Resource Graph is a powerful CMDB solution for querying Azure resources, they can be used wisely to enhance existing scripts or create new robust and scalable automations.</p> PowerShell to C# & back - JSON Create, Beautify https://dexterposh.github.io/posts/011-dotnet-pwsh-json/ Mon, 05 Oct 2020 09:07:45 +0530 https://dexterposh.github.io/posts/011-dotnet-pwsh-json/ <h2 id="background">Background</h2> <p>I have been following up the <a href="http://zetcode.com/csharp/json/">C# Tutorial</a> and working my way through examples and converting them to PowerShell and notes.</p> <h2 id="json-create-object">JSON Create object</h2> <p>Interesting to see that <code>System.Text.Json</code> namespace offers <code>Utf8JsonWriter</code> type to write JSON (UTF-8 encoded) string from common .NET types e.g. <code>String</code>, <code>Int32</code> and <code>DateTime</code>. What this means is we can write custom JSON strings in-memory (using MemoryStream) and later flush <code>Utf8JsonWriter</code> content to get the string back. I wanted to use this new writer to create a JSON string which will look like below</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="color:#f92672">&#34;ComputerName&#34;</span>: <span style="color:#e6db74">&#34;TestVM01&#34;</span> } </code></pre></div><p>Let&rsquo;s do this in C# first. Below is a C# script which I am using.</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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System; <span style="color:#66d9ef">using</span> System.Text; <span style="color:#66d9ef">using</span> System.Text.Json; <span style="color:#66d9ef">using</span> System.Net; <span style="color:#66d9ef">using</span> System.IO; <span style="color:#66d9ef">using</span> (<span style="color:#66d9ef">var</span> memStream = <span style="color:#66d9ef">new</span> MemoryStream()) { <span style="color:#66d9ef">using</span> (<span style="color:#66d9ef">var</span> jsonWriter = <span style="color:#66d9ef">new</span> Utf8JsonWriter(memStream)) { jsonWriter.WriteStartObject(); <span style="color:#75715e">// this is the start of JSON Object </span><span style="color:#75715e"></span> jsonWriter.WriteString(<span style="color:#e6db74">&#34;ComputerName&#34;</span>, <span style="color:#e6db74">&#34;TestVM01&#34;</span>); jsonWriter.WriteEndObject(); <span style="color:#75715e">// this is the end of the JSON Object </span><span style="color:#75715e"></span> <span style="color:#75715e">//jsonWriter.Flush(); </span><span style="color:#75715e"></span> } <span style="color:#66d9ef">string</span> json = Encoding.UTF8.GetString(memStream.ToArray()); Console.WriteLine(json); } </code></pre></div><p>This effectively translates to below in PowerShell, see inline comments.</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-powershell" data-lang="powershell"><span style="color:#75715e">#!/usr/bin/env pwsh</span> using namespace System.IO using namespace System.Text using namespace System.Text.Json <span style="color:#75715e"># create a memory stream to hold the data in-memory</span> <span style="color:#75715e"># the memory stream is a performant way to hold data without locking the source</span> $memStream = <span style="color:#66d9ef">[MemoryStream]</span>::new() <span style="color:#75715e"># use the Utf8 Json Writer object</span> $jsonWriter = <span style="color:#66d9ef">[Utf8JsonWriter]</span>::new($memStream) <span style="color:#75715e"># indicate the JSON object is starting</span> $jsonWriter.WriteStartObject(); <span style="color:#75715e"># now the JSON string is written</span> $jsonWriter.WriteString(<span style="color:#e6db74">&#34;ComputerName&#34;</span>, <span style="color:#e6db74">&#34;TestVM01&#34;</span>); <span style="color:#75715e"># instruct the JSON object is ending</span> $jsonWriter.WriteEndObject(); <span style="color:#75715e"># flush the json writer content to the stream</span> $jsonWriter.Flush(); <span style="color:#75715e"># dispose of the json writer</span> $jsonWriter.Dispose(); <span style="color:#75715e"># Get the string from the byte[] array from the memory stream</span> <span style="color:#66d9ef">[Encoding]</span>::UTF8.GetString($memStream.ToArray()) <span style="color:#75715e"># dispose the memory stream once read the json string</span> $memStream.Dispose() </code></pre></div><p>Below is the sample output that gets generated. Note it is a regular JSON string, let&rsquo;s make it prettier in next section. <img src="https://dexterposh.github.io/static/011/JsonStringOutput.png" alt="JsonStringOutput"></p> <h2 id="beautify-json">Beautify JSON</h2> <p>This is where tapping into the underlying .NET APIs to learn something really shines, there are so many tweaks that can be applied.</p> <p>One such instance is to make out output JSON string look pretty, we can pass some options to our Utf8JsonWriter instance.</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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System; <span style="color:#66d9ef">using</span> System.Text; <span style="color:#66d9ef">using</span> System.Text.Json; <span style="color:#66d9ef">using</span> System.Net; <span style="color:#66d9ef">using</span> System.IO; <span style="color:#66d9ef">using</span> (<span style="color:#66d9ef">var</span> memStream = <span style="color:#66d9ef">new</span> MemoryStream()) { <span style="color:#66d9ef">using</span> (<span style="color:#66d9ef">var</span> jsonWriter = <span style="color:#66d9ef">new</span> Utf8JsonWriter(memStream, <span style="color:#66d9ef">new</span> JsonWriterOptions {Indented = <span style="color:#66d9ef">true</span>})) { jsonWriter.WriteStartObject(); <span style="color:#75715e">// this is the start of JSON Object </span><span style="color:#75715e"></span> jsonWriter.WriteString(<span style="color:#e6db74">&#34;ComputerName&#34;</span>, <span style="color:#e6db74">&#34;TestVM01&#34;</span>); jsonWriter.WriteEndObject(); <span style="color:#75715e">// this is the end of the JSON Object </span><span style="color:#75715e"></span> <span style="color:#75715e">//jsonWriter.Flush(); </span><span style="color:#75715e"></span> } <span style="color:#66d9ef">string</span> json = Encoding.UTF8.GetString(memStream.ToArray()); Console.WriteLine(json); } </code></pre></div><p>This is pretty similar in PowerShell as well, create options for the <code>Utf8JsonWriter</code> and pass them.</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-powershell" data-lang="powershell"><span style="color:#75715e">#!/usr/bin/env pwsh</span> using namespace System.IO using namespace System.Text using namespace System.Text.Json <span style="color:#75715e"># create a memory stream to hold the data in-memory</span> <span style="color:#75715e"># the memory stream is a performant way to hold data without locking the source</span> $memStream = <span style="color:#66d9ef">[MemoryStream]</span>::new() <span style="color:#75715e">&lt;# </span><span style="color:#75715e">create Json writer options which is a structure (value-type) using PowerShell cast initialization technique </span><span style="color:#75715e">or you could do below: </span><span style="color:#75715e">$jsonWriterOptions = [JsonWriterOptions]::new(); $jsonWriterOptions.Iden </span><span style="color:#75715e">$jsonWriterOptions.Indented = $true </span><span style="color:#75715e">#&gt;</span> $jsonWriterOptions = <span style="color:#66d9ef">[JsonWriterOptions]</span>@{<span style="color:#e6db74">&#34;Indented&#34;</span> = $true} <span style="color:#75715e"># use the Utf8 Json Writer object</span> $jsonWriter = <span style="color:#66d9ef">[Utf8JsonWriter]</span>::new($memStream, $jsonWriterOptions) <span style="color:#75715e"># indicate the JSON object is starting</span> $jsonWriter.WriteStartObject(); <span style="color:#75715e"># now the JSON string is written</span> $jsonWriter.WriteString(<span style="color:#e6db74">&#34;ComputerName&#34;</span>, <span style="color:#e6db74">&#34;TestVM01&#34;</span>); <span style="color:#75715e"># instruct the JSON object is ending</span> $jsonWriter.WriteEndObject(); <span style="color:#75715e"># flush the json writer content to the stream</span> $jsonWriter.Flush(); <span style="color:#75715e"># dispose of the json writer</span> $jsonWriter.Dispose(); <span style="color:#75715e"># Get the string from the byte[] array from the memory stream</span> <span style="color:#66d9ef">[Encoding]</span>::UTF8.GetString($memStream.ToArray()) <span style="color:#75715e"># dispose the memory stream once read the json string</span> $memStream.Dispose() </code></pre></div><p>Output is below: <img src="https://dexterposh.github.io/static/011/beautifyJsonString.png" alt="Beautify JSON string output"></p> <h2 id="conclusion">Conclusion</h2> <p>As a PowerShell developer, I learned using <code>Utf8JsonWriter</code> to create JSON strings from scratch and beautify them.</p> <p>There will be a follow up post on this soon&hellip;</p> <h2 id="reference-links-">Reference links πŸ“–</h2> <p><a href="https://leanpub.com/powershell-to-csharp">PowerShell to C# &amp; Back leanpub book</a></p> <p><a href="http://zetcode.com/csharp/json/">C# JSON Tutorial</a></p> PowerShell to C# & back - JSON Seriazlie & Deserialize https://dexterposh.github.io/posts/010-dotnet-pwsh-json/ Fri, 25 Sep 2020 07:12:33 +0530 https://dexterposh.github.io/posts/010-dotnet-pwsh-json/ <h2 id="background">Background</h2> <p>In the below previous post</p> <ul> <li><a href="https://dexterposh.github.io/posts/009-dotnet-pwsh-json/">PowerShell to C# &amp; back - JSON parse &amp; enumerate</a></li> </ul> <p>there were notes on how to enumerate &amp; iterate over JSON documents. Let&rsquo;s continue down the rabbit hole. In this post we will look at how to use <code>System.Text.Json</code> namespace to:</p> <ul> <li>Serialize to JSON</li> <li>Deserialize from JSON</li> </ul> <h2 id="serialze-to-json">Serialze to JSON</h2> <p>This means converting any type into a JSON string. We start off with a type named <code>Computer</code> and convert it into a JSON string. We call the <code>JsonSerializer.Serialze()</code> static method for this operation.</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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System; <span style="color:#66d9ef">using</span> System.Text.Json; <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Computer</span> { <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string</span> hostName { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; } <span style="color:#75715e">// In C# 6 and later, we can assign a default value to an auto property </span><span style="color:#75715e"></span> <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string</span> domainName { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; } = <span style="color:#e6db74">&#34;contoso.com&#34;</span>; <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string</span> IpAddress { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; } } <span style="color:#66d9ef">var</span> machine1 = <span style="color:#66d9ef">new</span> Computer() { hostName = <span style="color:#e6db74">&#34;testvm01&#34;</span>, IpAddress = <span style="color:#e6db74">&#34;10.10.10.101&#34;</span> }; <span style="color:#75715e">// serialize the object into JSON string </span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> json = JsonSerializer.Serialize(machine1); Console.WriteLine(json); </code></pre></div><p>Now doing the same in PowerShell πŸ€‘</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-powershell" data-lang="powershell"><span style="color:#75715e">#!/usr/bin/env pwsh</span> using namespace System.Text.Json class Computer { <span style="color:#66d9ef">[string]</span> $hostName <span style="color:#66d9ef">[string]</span> $domainName = <span style="color:#e6db74">&#34;contoso.com&#34;</span> <span style="color:#75715e"># this is the initial value</span> <span style="color:#66d9ef">[string]</span> $IpAddress } $machine1 = <span style="color:#66d9ef">[Computer]</span> @{ hostName = <span style="color:#e6db74">&#34;testvm01&#34;</span> IpAddress = <span style="color:#e6db74">&#34;10.10.10.101&#34;</span> } <span style="color:#75715e"># Invoke the static method on the class, it requires 2 arguments</span> <span style="color:#75715e"># the Value to convert and any JsonSerializer options which we have passed as $null</span> $json = <span style="color:#66d9ef">[JsonSerializer]</span>::Serialize($machine1, $null) Write-Host -Object $json </code></pre></div><h2 id="deserialize-from-json">Deserialize from JSON</h2> <p>This means constructing a typed object from JSON string. No surprise there exists a <code>JsonSerializer.Deserialze()</code> static method which can take a JSON string as input and can deserialized it into a typed object instance.</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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System; <span style="color:#66d9ef">using</span> System.Text.Json; <span style="color:#66d9ef">using</span> System.Net; <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Computer</span> { <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string</span> hostName { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; } <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string</span> domainName { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; } = <span style="color:#e6db74">&#34;contoso.com&#34;</span>; <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string</span> IpAddress { <span style="color:#66d9ef">get</span>; <span style="color:#66d9ef">set</span>; } } <span style="color:#66d9ef">string</span> jsonComputer = <span style="color:#e6db74">&#34;{\&#34;domainName\&#34;:\&#34;contoso.com\&#34;,\&#34;hostName\&#34;:\&#34;testvm01\&#34;,\&#34;IpAddress\&#34;:\&#34;10.10.10.101\&#34;}&#34;</span>; <span style="color:#66d9ef">var</span> computerObject = JsonSerializer.Deserialize&lt;Computer&gt;(jsonComputer); Console.WriteLine(computerObject.domainName); </code></pre></div><p>In PowerShell the deserialization works really well with classes. If we have a type defined in PowerShell class, we can use the <code>Deserialze()</code> static method and pass it the type to deserialize the JSON string into.</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-powershell" data-lang="powershell"><span style="color:#75715e">#!/usr/bin/env pwsh</span> using namespace System.Text.Json class Computer { <span style="color:#66d9ef">[string]</span> $hostName <span style="color:#66d9ef">[string]</span> $domainName = <span style="color:#e6db74">&#34;contoso.com&#34;</span> <span style="color:#75715e"># this is the initial value</span> <span style="color:#66d9ef">[string]</span> $IpAddress } $jsonComputer = <span style="color:#e6db74">&#39;{&#34;hostName&#34;:&#34;testvm01&#34;,&#34;IpAddress&#34;:&#34;10.10.10.101&#34;}&#39;</span> <span style="color:#75715e"># Invoke the Deserialize static method on the class, it requires 3 arguments</span> <span style="color:#75715e"># the Value to convert, and the type to convert it into and JsonSerializer options which we have passed as $null</span> $computer = <span style="color:#66d9ef">[JsonSerializer]</span>::Deserialize($jsonComputer, <span style="color:#66d9ef">[Computer]</span>, $null) <span style="color:#75715e"># $computer | Get-Member # verify that the type got back is a Computer object type</span> Write-Host -Object $computer.domainName </code></pre></div><h2 id="bonus">Bonus</h2> <p>Above way to deserialize the JSON string into a typed object in PowerShell is pretty neat but we do mention another subtle way to achieve the same in our <a href="https://leanpub.com/powershell-to-csharp">Book</a>.</p> <p>Cast-Initialization technique, which works by invoking an empty constrcutor and initializing values.</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-powershell" data-lang="powershell">$jsonComputer = <span style="color:#e6db74">&#39;{&#34;hostName&#34;:&#34;testvm01&#34;,&#34;IpAddress&#34;:&#34;10.10.10.101&#34;}&#39;</span> <span style="color:#66d9ef">[Computer]</span>($jsonComputer | ConvertFrom-Json) </code></pre></div><h2 id="conclusion-">Conclusion πŸ—ž</h2> <p>As a PowerShell developer, I learned using <code>System.Text.Json</code> namespace in PowerShell core to serialize/deserialize JSON in this post.</p> <p>There will be a follow up post on this soon&hellip;</p> <h2 id="reference-links-">Reference links πŸ“–</h2> <p><a href="https://leanpub.com/powershell-to-csharp">PowerShell to C# &amp; Back leanpub book</a></p> <p><a href="http://zetcode.com/csharp/json/">C# JSON Tutorial</a></p> PowerShell to C# & back - JSON parse & enumerate https://dexterposh.github.io/posts/009-dotnet-pwsh-json/ Wed, 16 Sep 2020 11:12:33 +0530 https://dexterposh.github.io/posts/009-dotnet-pwsh-json/ <h2 id="background">Background</h2> <p>Coming from PowerShell background, while learning ASP.NET Core based development I wanted to wrap my head around how to handle JSON, so ended up taking notes on how to do this in C# and as an exercise convert those into PowerShell code snippets for my reference.</p> <p>In my explorations I like using the dotnet-script global tool for interactive user experience with C# and PowerShell core (which is already an interactive shell). So, you will see that I mostly use C# script files (.csx) and PowerShell script files (.ps1).</p> <blockquote> <p>Giving the credit where it&rsquo;s due, I followed this <a href="http://zetcode.com/csharp/json/">tutorial</a> to learn JSON handling in C# and made these notes of my own.</p> </blockquote> <p>In this post the topics covered are:</p> <ul> <li>Parsing JSON</li> <li>Enumerating JSON</li> </ul> <blockquote> <p><strong>System.Text.Json</strong></p> <p>This is the new namespace in .NET core which provides the API to work with JSON.</p> </blockquote> <h2 id="json-parsing">JSON parsing</h2> <p>JSON parsing is basically reading the JSON data and converting it into a <code>JsonDocument</code> typed object in C#.</p> <p>Long story short:</p> <ul> <li>Use the static method <code>Parse</code> on the <code>JsonDocument</code> class to consume a JSON string and get an object back.</li> <li><code>RootElement</code> on the instance of <code>JsonDocument</code> gives you the entire JSON object</li> <li>Root element if it contains array can be indexed</li> <li>To get the value of a property on the JSON object use <code>GetProperty('propertyName')</code></li> </ul> <p>Below are the contents of a test.csx 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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System; <span style="color:#66d9ef">using</span> System.Text.Json; <span style="color:#66d9ef">string</span> data = <span style="color:#e6db74">&#34; [ {\&#34;name\&#34;: \&#34;John Doe\&#34;, \&#34;occupation\&#34;: \&#34;gardener\&#34;}, &#34;</span> + <span style="color:#e6db74">&#34;{\&#34;name\&#34;: \&#34;Peter Novak\&#34;, \&#34;occupation\&#34;: \&#34;driver\&#34;} ]&#34;</span>; <span style="color:#75715e">// using statement is used to dispose of the JsonDocument object later </span><span style="color:#75715e">// Parse static method parses the JSON string and returns a JsonDocument instance </span><span style="color:#75715e"></span><span style="color:#66d9ef">using</span> (JsonDocument doc = JsonDocument.Parse(data)) { <span style="color:#66d9ef">var</span> root = doc.RootElement; <span style="color:#75715e">// RootElement is the real object we are after </span><span style="color:#75715e"></span> <span style="color:#66d9ef">var</span> user1 = root[<span style="color:#ae81ff">0</span>]; <span style="color:#75715e">// since our object contains an array, we can index into it </span><span style="color:#75715e"></span> <span style="color:#66d9ef">var</span> user2 = root[<span style="color:#ae81ff">2</span>]; <span style="color:#75715e">// Use the method GetProperty() to get properties of the JsonElement object </span><span style="color:#75715e"></span> Console.WriteLine(user1.GetProperty(<span style="color:#e6db74">&#34;name&#34;</span>)); <span style="color:#75715e">// GetProperty() </span><span style="color:#75715e"></span> Console.WriteLine(user2.GetProperty(<span style="color:#e6db74">&#34;name&#34;</span>)); } </code></pre></div><p>To do the same in PowerShell, below are the contents of a test.ps1 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-powershell" data-lang="powershell"><span style="color:#75715e">#!/usr/bin/env pwsh</span> using namespace System.Text.Json $data = <span style="color:#e6db74">&#39;[{&#34;name&#34;: &#34;John Doe&#34;, &#34;occupation&#34;: &#34;gardener&#34;},&#39;</span> + <span style="color:#e6db74">&#39;{&#34;name&#34;: &#34;Peter Novak&#34;, &#34;occupation&#34;: &#34;driver&#34;} ]&#39;</span>; $doc = <span style="color:#66d9ef">[JsonDocument]</span>::Parse($data) $root = $doc.RootElement $user1 = $root[0] $user2 = $root[1] Write-Host $User1.GetProperty(<span style="color:#e6db74">&#34;name&#34;</span>) Write-Host $User2.GetProperty(<span style="color:#e6db74">&#34;name&#34;</span>) <span style="color:#75715e"># dispose the JsonDocument once done</span> <span style="color:#75715e"># We don&#39;t do this in C# coz of the using statement automatically doing this</span> $doc.Dispose() </code></pre></div><h2 id="json-enumerate">JSON Enumerate</h2> <p>In the above example we indexed into the elements of the array directly and then used <code>GetProperty()</code> this only works if you know the number of elements in advance along with property names, but there are better ways to iterate and enumerate.</p> <p>Trick for someone new is to remember:</p> <ul> <li>Enumerating arrays to fetch different items</li> <li>Enumerating objects to fetch properties.</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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System; <span style="color:#66d9ef">using</span> System.Text.Json; <span style="color:#66d9ef">string</span> data = <span style="color:#e6db74">&#34; [ {\&#34;name\&#34;: \&#34;John Doe\&#34;, \&#34;occupation\&#34;: \&#34;gardener\&#34;}, &#34;</span> + <span style="color:#e6db74">&#34;{\&#34;name\&#34;: \&#34;Peter Novak\&#34;, \&#34;occupation\&#34;: \&#34;driver\&#34;} ]&#34;</span>; <span style="color:#66d9ef">using</span> (<span style="color:#66d9ef">var</span> doc = JsonDocument.Parse(data)) { JsonElement root = doc.RootElement; <span style="color:#75715e">// call method EnumerateArray() to iterate over items in the array </span><span style="color:#75715e"></span> <span style="color:#66d9ef">foreach</span> (<span style="color:#66d9ef">var</span> user <span style="color:#66d9ef">in</span> root.EnumerateArray()) { Console.WriteLine(<span style="color:#e6db74">&#34;====== User details ====&#34;</span>); <span style="color:#75715e">// EnumerateObject() method is used return an ObjectEnumerator </span><span style="color:#75715e"></span> <span style="color:#75715e">// which is then used to get the Name &amp; Value pair for each property </span><span style="color:#75715e"></span> <span style="color:#66d9ef">foreach</span> (<span style="color:#66d9ef">var</span> prop <span style="color:#66d9ef">in</span> user.EnumerateObject()) { Console.WriteLine(<span style="color:#e6db74">$&#34;{prop.Name} -&gt; {prop.Value}&#34;</span>); } } } </code></pre></div><p>Doing the same in PowerShell makes it easier to grasp.</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-powershell" data-lang="powershell"><span style="color:#75715e">#!/usr/bin/env pwsh</span> using namespace System.Text.Json $data = <span style="color:#e6db74">&#34;[ {</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">name</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">: </span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">John Doe</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">, </span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">occupation</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">: </span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">gardener</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">}, &#34;</span> + <span style="color:#e6db74">&#34;{</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">name</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">: </span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">Peter Novak</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">, </span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">occupation</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">: </span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">driver</span><span style="color:#ae81ff">`&#34;</span><span style="color:#e6db74">} ]&#34;</span>; $doc = <span style="color:#66d9ef">[JsonDocument]</span>::Parse($data) $root = $doc.RootElement <span style="color:#66d9ef">foreach</span> ($user <span style="color:#66d9ef">in</span> $root.EnumerateArray()) { Write-Host <span style="color:#e6db74">&#34;====== User details ====&#34;</span> <span style="color:#66d9ef">foreach</span> ($property <span style="color:#66d9ef">in</span> $user.EnumerateObject()) { Write-Host $(<span style="color:#e6db74">&#34;{0} -&gt; {1}&#34;</span> <span style="color:#f92672">-f</span> $property.Name, $property.Value) } } </code></pre></div><h2 id="conclusion-">Conclusion πŸ—ž</h2> <p>In my continued efforts to learn C# language and take some of the learnings back to PowerShell, I realized that though the JSON cmdlets provide a lot of ease working with JSON, there are other ways to skin the cat.</p> <p>There will be a follow up post on this soon&hellip;</p> <h2 id="reference-links-">Reference links πŸ“–</h2> <p><a href="https://leanpub.com/powershell-to-csharp">PowerShell to C# &amp; Back leanpub book</a></p> <p><a href="http://zetcode.com/csharp/json/">C# JSON Tutorial</a></p> PowerShell Classes - Validating ARM parameters https://dexterposh.github.io/posts/007-pwsh-class-usecase/ Mon, 29 Jun 2020 19:42:59 +0530 https://dexterposh.github.io/posts/007-pwsh-class-usecase/ <h2 id="origin">Origin❓</h2> <p>(shameless plug, alert!) πŸ™ƒ</p> <p>Recently, I was discussing with my colleague about this new <a href="https://leanpub.com/powershell-to-csharp">book</a> I am co-authoring (with <a href="https://twitter.com/singhprateik">Prateek</a>) about why to learn .NET to be a better PowerShell programmer and upon further discussion we pondered some interesting ways to use PowerShell classes.</p> <h2 id="brain-storming-">Brain-storming πŸ€”</h2> <p>All was lost, until we had another quick conversation about how to validate ARM templates. Well, I suggested to write Pester tests to check the input being passed and perform <code>Test-AzDeployment</code> for ARM templates.</p> <p>Another idea that popped up in my mind was what if we write a PowerShell class to model the ARM parameters file and use that to validate the ARM template parameter inputs.</p> <h2 id="solution-">Solution πŸš€</h2> <p>There exists the <a href="https://github.com/dfinke/ConvertToClass">ConvertToClass</a> module by Doug Finke, which comes to the rescue to automatically convert a JSON object to a PowerShell class. It even has VSCode integration, check it out.</p> <p>Let&rsquo;s start by taking a sample <code>azuredeploy.parameters.json</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-json" data-lang="json">{ <span style="color:#f92672">&#34;$schema&#34;</span>: <span style="color:#e6db74">&#34;https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#&#34;</span>, <span style="color:#f92672">&#34;contentVersion&#34;</span>: <span style="color:#e6db74">&#34;1.0.0.0&#34;</span>, <span style="color:#f92672">&#34;parameters&#34;</span>: { <span style="color:#f92672">&#34;storageAccountNamePrefix&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;Azurearm6754&#34;</span> }, <span style="color:#f92672">&#34;numberofStorageAccounts&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#ae81ff">2</span> }, <span style="color:#f92672">&#34;storageAccountType&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;Standard_LRS&#34;</span> }, <span style="color:#f92672">&#34;location&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;westus2&#34;</span> } } } </code></pre></div><h3 id="convert-json-to-class-">Convert Json to Class ☯</h3> <p>Let&rsquo;s run the <code>ConverTo-Class</code> function in the ConvertToClass module against this <code>azuredeploy.parameters.json</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-powershell" data-lang="powershell">ConvertTo-Class -Target (Get-Content ./azuredeploy.parameters.json -Raw) </code></pre></div><p><img src="https://dexterposh.github.io/static/007/class1.png" alt="alt"></p> <p>Above command generates below content:</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-powershell" data-lang="powershell">class RootObject1 { <span style="color:#66d9ef">[string]</span>$$schema <span style="color:#66d9ef">[string]</span>$contentVersion <span style="color:#66d9ef">[parameters]</span>$parameters } class parameters1 { <span style="color:#66d9ef">[storageAccountNamePrefix]</span>$storageAccountNamePrefix <span style="color:#66d9ef">[numberofStorageAccounts]</span>$numberofStorageAccounts <span style="color:#66d9ef">[storageAccountType]</span>$storageAccountType <span style="color:#66d9ef">[location]</span>$location } class storageAccountNamePrefix1 { <span style="color:#66d9ef">[string]</span>$value } class numberofStorageAccounts1 { <span style="color:#66d9ef">[int]</span>$value } class storageAccountType1 { <span style="color:#66d9ef">[string]</span>$value } class location1 { <span style="color:#66d9ef">[string]</span>$value } </code></pre></div><p>Let&rsquo;s rename the above classes like below:</p> <ul> <li><code>$$schema</code> property on <code>RootObject1</code> to <code>${$schema}</code>, done to escape <code>$</code> char in property name</li> <li><code>RootObject1</code> to <code>AzureParameters</code></li> <li><code>parameters1</code> to <code>Parameters</code></li> <li><code>storageAccountNamePrefix1</code> to <code>StorageAccountType</code></li> <li><code>numberofStorageAccounts1</code> to <code>StorageAccountType</code></li> <li><code>storageAccountType1</code> to <code>StorageAccountType</code></li> <li><code>location1</code> to <code>Location</code></li> </ul> <h3 id="add-validation-attributes-">Add validation attributes πŸ”¨</h3> <p>We can add validation attributes to the property <code>$value</code> present inside the auto-generated class to add some quick validation rules to the properties present on the class.</p> <blockquote> <p>Note the name <code>$value</code> is given to the property because this is how ARM parameters file take input.</p> </blockquote> <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-powershell" data-lang="powershell">class AzureParameters { <span style="color:#66d9ef">[string]</span>${$schema} <span style="color:#66d9ef">[string]</span>$contentVersion <span style="color:#66d9ef">[Parameters]</span>$parameters } class Parameters { <span style="color:#66d9ef">[StorageAccountNamePrefix]</span>$storageAccountNamePrefix <span style="color:#66d9ef">[NumberofStorageAccounts]</span>$numberofStorageAccounts <span style="color:#66d9ef">[StorageAccountType]</span>$storageAccountType <span style="color:#66d9ef">[Location]</span>$location } class StorageAccountNamePrefix { <span style="color:#75715e"># validate the prefix length</span> [<span style="color:#66d9ef">ValidateLength</span>(3,15)] <span style="color:#66d9ef">[string]</span>$value } class StorageAccountNamePrefix { <span style="color:#75715e"># restricting the min=1 and max=10 storage accounts that one can request</span> [<span style="color:#66d9ef">ValidateRange</span>(1,10)] <span style="color:#66d9ef">[int]</span>$value } class StorageAccountType { <span style="color:#75715e"># Restricting the StorageAccount SKUs</span> [<span style="color:#66d9ef">ValidateSet</span>(<span style="color:#e6db74">&#39;Standard_LRS&#39;</span>, <span style="color:#e6db74">&#39;Standard_GRS&#39;</span>, <span style="color:#e6db74">&#39;Standard_ZRS&#39;</span>)] <span style="color:#66d9ef">[string]</span>$value } class Location { <span style="color:#75715e"># Restricting the locations</span> [<span style="color:#66d9ef">ValidateSet</span>(<span style="color:#e6db74">&#39;westus2&#39;</span>, <span style="color:#e6db74">&#39;northcentralus&#39;</span>)] <span style="color:#66d9ef">[string]</span>$value } </code></pre></div><h3 id="add-logic-inside-empty-constructor-">Add logic inside Empty Constructor βŒ›</h3> <p>We can add one more trick to the bag to add an empty constructor explicitly (this is present when no constructor exists) and put some more validation logic if the current validate attributes doesn' suit the needs.</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-powershell" data-lang="powershell">class AzureParameters { <span style="color:#66d9ef">[string]</span>${$schema} <span style="color:#66d9ef">[string]</span>$contentVersion <span style="color:#66d9ef">[Parameters]</span>$parameters } class Parameters { <span style="color:#66d9ef">[StorageAccountNamePrefix]</span>$storageAccountNamePrefix <span style="color:#66d9ef">[NumberofStorageAccounts]</span>$numberofStorageAccounts <span style="color:#66d9ef">[StorageAccountType]</span>$storageAccountType <span style="color:#66d9ef">[Location]</span>$location } class StorageAccountNamePrefix { <span style="color:#75715e"># validate the prefix length</span> [<span style="color:#66d9ef">ValidateLength</span>(3,15)] <span style="color:#66d9ef">[string]</span>$value } class NumberofStorageAccounts { <span style="color:#75715e"># restricting the min=1 and max=10 storage accounts that one can request</span> [<span style="color:#66d9ef">ValidateRange</span>(1,10)] <span style="color:#66d9ef">[int]</span>$value } class StorageAccountType { <span style="color:#75715e"># Restricting the StorageAccount SKUs</span> [<span style="color:#66d9ef">ValidateSet</span>(<span style="color:#e6db74">&#39;Standard_LRS&#39;</span>, <span style="color:#e6db74">&#39;Standard_GRS&#39;</span>, <span style="color:#e6db74">&#39;Standard_ZRS&#39;</span>)] <span style="color:#66d9ef">[string]</span>$value <span style="color:#75715e"># Add some more validation logic inside the empty constructor</span> storageAccountType() { <span style="color:#75715e"># Perform some validation on the property</span> <span style="color:#66d9ef">if</span> ($this.value <span style="color:#f92672">-ne</span> <span style="color:#e6db74">&#39;Standard_ZRS&#39;</span>) { <span style="color:#75715e"># you can perform more checks or logging, throwing a warning here</span> Write-Warning -Message <span style="color:#e6db74">&#34;No zone redundancy&#34;</span> } } } class Location { <span style="color:#75715e"># Restricting the locations</span> [<span style="color:#66d9ef">ValidateSet</span>(<span style="color:#e6db74">&#39;westus2&#39;</span>, <span style="color:#e6db74">&#39;northcentralus&#39;</span>)] <span style="color:#66d9ef">[string]</span>$value } </code></pre></div><blockquote> <p>Why does this has to be an empty constructor? The answer is that we will be a using a trick with how we create an instance of the class in next section</p> </blockquote> <h3 id="secret-sauce--cast-initialization-">Secret-Sauce : Cast-Initialization 🍲</h3> <p>Credit goes to Bruce Payette&rsquo;s <a href="https://livebook.manning.com/book/windows-powershell-in-action-third-edition/chapter-19/311">Windows PowerShell in Action 3rd</a> edition which talks a bit about this technique in brief.</p> <p>In short, you can take a hashtable or PSObjects and cast them as strongly type object instance. This will become more clear when we perform this casting a bit later.</p> <blockquote> <p>For cast-initalization technique to work an empty constructor needs to be present in the Class definition. It is present by default if there is no constructor on the class present or you can add one explicitly.</p> </blockquote> <p>Modify some value in the <code>azuredeploy.parameters.json</code> file to not-follow some validation logic we added e.g. putting the value for <code>numberofStorageAccount</code> as <code>20</code>, remember our <code>[ValidateRange(1,10)]</code> attribute on the property should fails. See the parameters file below:</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="color:#f92672">&#34;$schema&#34;</span>: <span style="color:#e6db74">&#34;https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#&#34;</span>, <span style="color:#f92672">&#34;contentVersion&#34;</span>: <span style="color:#e6db74">&#34;1.0.0.0&#34;</span>, <span style="color:#f92672">&#34;parameters&#34;</span>: { <span style="color:#f92672">&#34;storageAccountNamePrefix&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;azurearm6754&#34;</span> }, <span style="color:#f92672">&#34;numberofStorageAccounts&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#ae81ff">20</span> }, <span style="color:#f92672">&#34;storageAccountType&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;Standard_LRS&#34;</span> }, <span style="color:#f92672">&#34;location&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;westus2&#34;</span> } } } </code></pre></div><p>Now, see the result when casting this as our <code>ArmParameters</code> class.</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-powershell" data-lang="powershell"><span style="color:#66d9ef">[azureparameters]</span> (Get-Content -Path $PSScriptRoot/azuredeploy.parameters.json | ConvertFrom-Json) </code></pre></div><p><img src="https://dexterposh.github.io/static/007/validation.png" alt="Validation in Action"></p> <p>Throws below error:</p> <pre tabindex="0"><code class="language-error" data-lang="error">InvalidArgument: Cannot convert value &quot;@{$schema=https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#; contentVersion=1.0.0.0; parameters=}&quot; to type &quot;AzureParameters&quot;. Error: &quot;Cannot convert value &quot;@{storageAccountNamePrefix=; numberofStorageAccounts=; storageAccountType=; location=}&quot; to type &quot;Parameters&quot;. Error: &quot;Cannot create object of type &quot;NumberofStorageAccounts&quot;. The 20 argument is greater than the maximum allowed range of 10. Supply an argument that is less than or equal to 10 and then try the command again.&quot;&quot; </code></pre><p>Fix the value for the <code>numberofStorageAccounts</code> parameter.</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="color:#f92672">&#34;$schema&#34;</span>: <span style="color:#e6db74">&#34;https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#&#34;</span>, <span style="color:#f92672">&#34;contentVersion&#34;</span>: <span style="color:#e6db74">&#34;1.0.0.0&#34;</span>, <span style="color:#f92672">&#34;parameters&#34;</span>: { <span style="color:#f92672">&#34;storageAccountNamePrefix&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;azurearm6754&#34;</span> }, <span style="color:#f92672">&#34;numberofStorageAccounts&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#ae81ff">2</span> }, <span style="color:#f92672">&#34;storageAccountType&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;Standard_LRS&#34;</span> }, <span style="color:#f92672">&#34;location&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;westus2&#34;</span> } } } </code></pre></div><p>Run the below again:</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-powershell" data-lang="powershell"><span style="color:#66d9ef">[azureparameters]</span> (Get-Content -Path $PSScriptRoot/azuredeploy.parameters.json | ConvertFrom-Json) </code></pre></div><p><img src="https://dexterposh.github.io/static/007/valid.png" alt="Validation passes"></p> <h2 id="conclusion-">Conclusion βœ…</h2> <p>This is maybe a very basic way of validating input data but I read about a topic and used it to solve a problem in a unique way, which is a win for me 😎.</p> <p>I think slowly embracing more Object-Oriented programming using PowerShell classes (or C#) can open up some interesting ways to solve problems in my tooling.</p> <h2 id="resource-links-">Resource links πŸ“š</h2> <ul> <li><a href="https://leanpub.com/powershell-to-csharp">PowerShell to C# and back</a> - Disclaimer: co-author on this one.</li> <li><a href="https://ridicurious.com/2020/06/29/powershell-to-csharp-and-back-classes/">PowerShell to C# and Back – Introduction to Classes</a></li> <li><a href="https://livebook.manning.com/book/windows-powershell-in-action-third-edition">Windows PowerShell in Action, 3rd Edition</a></li> <li><a href="https://github.com/dfinke/ConvertToClass">Doug Finke&rsquo;s ConvertToClass module</a></li> <li><a href="https://github.com/Stephanevg/PSClassUtils">PSClassUtils</a></li> </ul> .NET notes - create SHA256 hash https://dexterposh.github.io/posts/003-dotnet-sha256-hash/ Fri, 05 Jun 2020 10:18:40 +0530 https://dexterposh.github.io/posts/003-dotnet-sha256-hash/ <h2 id="background-">Background 🧐</h2> <p>Today I was looking to generate SHA256 hash for input string data. Below are my notes on how I used dotnet script (interactive scripting experience) in .NET to experiment with it.</p> <p>P.S. - Writing these small .NET recipes helps me in absorbing more.</p> <h2 id="walkthrough-">Walkthrough ⚑</h2> <h3 id="using-dotnet-script">Using dotnet script</h3> <p>To quickly test features in .NET core, I use the <a href="https://www.nuget.org/packages/dotnet-script/"><em>dotnet script</em></a> global tool.</p> <p>Begin with creating a dotnet script file (.csx extension).</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">mkdir hashing cd hashing dotnet script init sha256hash </code></pre></div><p>Above dotnet command creates an executable file with below content.</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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script Console.WriteLine(<span style="color:#e6db74">&#34;Hello world!&#34;</span>); </code></pre></div><p>Since this is an executable script, we can run it like this 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-bash" data-lang="bash">./sha256hash.csx </code></pre></div><p>Output:</p> <pre tabindex="0"><code class="language-output" data-lang="output">Hello world! </code></pre><h3 id="using-statements-in-scripts">Using statements in scripts</h3> <p>Now, let&rsquo;s modify this script to explore creating a SHA256 hash.</p> <p>First, thing is we need to place some using statements to bring in the Cryptography and Text namespace like below.</p> <p>So the content now looks like.</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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#75715e">// Add the using statements </span><span style="color:#75715e"></span><span style="color:#66d9ef">using</span> System.Text; <span style="color:#66d9ef">using</span> System.Security.Cryptography; </code></pre></div><h3 id="reading-the-docs">Reading the docs</h3> <p>Moving on, after browsing the documentation of the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha256.create?view=netcore-3.1#System_Security_Cryptography_SHA256_Create">SHA256 class</a> I noticed a <em>Create()</em> method which creates a default instance.</p> <p>Also, noticed that the inheritance chain for this class is Object -&gt; HashAlgorithm -&gt; SHA256</p> <p>HashAlgorithm is the base class with below signature, notice it in turn inherits from IDisposable interface this immediately reminded me to use the <code>using</code> statement syntax to conveniently dispose this object after re-use, rather than calling <code>Dispose()</code> method myself inside try/catch/finally statements.</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-csharp" data-lang="csharp"><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">abstract</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">HashAlgorithm</span> : IDisposable, System.Security.Cryptography.ICryptoTransform </code></pre></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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System.Text; <span style="color:#66d9ef">using</span> System.Security.Cryptography; <span style="color:#66d9ef">string</span> text = <span style="color:#e6db74">&#34;DexterPOSH&#34;</span>; <span style="color:#75715e">// this is our text for which we will generate hash </span><span style="color:#75715e"></span> <span style="color:#66d9ef">using</span> (SHA256 hashAlgorithm = SHA256.Create()) { <span style="color:#75715e">// place holder </span><span style="color:#75715e"></span>}; </code></pre></div><p>The docs also reveal the <code>ComputeHash()</code> method which takes <code>byte[]</code> array as argument and returns the byte array back as well. We need some way to convert our string input to byte array.</p> <h3 id="string-to-byte-conversion">string to byte[] conversion</h3> <p>Quick search suggests to use <code>Encoding.UTF8.GetBytes()</code> static method for converting string to byte array. Using that in code now leads us to this 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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System.Text; <span style="color:#66d9ef">using</span> System.Security.Cryptography; <span style="color:#66d9ef">string</span> text = <span style="color:#e6db74">&#34;DexterPOSH&#34;</span>; <span style="color:#75715e">// this is our text for which we will generate hash </span><span style="color:#75715e"></span> <span style="color:#66d9ef">using</span> (SHA256 hashAlgorithm = SHA256.Create()) { <span style="color:#66d9ef">var</span> hashedByteArray = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); Console.WriteLine(hashedByteArray); } </code></pre></div><p>Output:</p> <pre tabindex="0"><code class="language-output" data-lang="output">System.Byte[] </code></pre><p>Let&rsquo;s see how we can convert the byte[] to string object.</p> <h3 id="byte-to-string-conversion">byte[] to string conversion</h3> <p>Again search and got a hint of using the <code>BitConverter.ToString()</code> static method. Let&rsquo;s add that logic in our script.</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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System.Text; <span style="color:#66d9ef">using</span> System.Security.Cryptography; <span style="color:#66d9ef">string</span> text = <span style="color:#e6db74">&#34;DexterPOSH&#34;</span>; <span style="color:#75715e">// this is our text for which we will generate hash </span><span style="color:#75715e"></span> <span style="color:#66d9ef">using</span> (SHA256 hashAlgorithm = SHA256.Create()) { <span style="color:#66d9ef">var</span> hashedByteArray = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); Console.WriteLine(BitConverter.ToString(hashedByteArray)); }; </code></pre></div><p>Output:</p> <pre tabindex="0"><code class="language-output" data-lang="output">1C-29-A2-30-0A-8A-99-6F-67-60-70-7E-21-0D-BD-61-B1-C9-3A-B7-4F-86-EE-13-7B-2E-DE-B6-01-6E-87-93 </code></pre><h3 id="remove---from-string">remove &lsquo;-&rsquo; from string</h3> <p>As the last step let&rsquo;s replace the char <code>-</code> from the output string. <code>String</code> class has replace method which invoke.</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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System.Text; <span style="color:#66d9ef">using</span> System.Security.Cryptography; <span style="color:#66d9ef">using</span> (SHA256 hashAlgorithm = SHA256.Create()) { <span style="color:#66d9ef">string</span> input = <span style="color:#e6db74">&#34;DexterPOSH&#34;</span>; <span style="color:#66d9ef">byte</span>[] data = hashAlgorithm.ComputeHash( Encoding.UTF8.GetBytes(input) ); Console.WriteLine(BitConverter.ToString(data).Replace(<span style="color:#e6db74">&#34;-&#34;</span>, String.Empty)); } </code></pre></div><p>Output:</p> <pre tabindex="0"><code class="language-output" data-lang="output">1C29A2300A8A996F6760707E210DBD61B1C93AB74F86EE137B2EDEB6016E8793 </code></pre><h2 id="solution-">Solution 😎</h2> <p>Content of the sha256hash.csx 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-csharp" data-lang="csharp"><span style="color:#960050;background-color:#1e0010">#</span>!/usr/bin/env dotnet-script <span style="color:#66d9ef">using</span> System.Text; <span style="color:#66d9ef">using</span> System.Security.Cryptography; <span style="color:#66d9ef">using</span> (SHA256 hashAlgorithm = SHA256.Create()) { <span style="color:#66d9ef">string</span> input = <span style="color:#e6db74">&#34;DexterPOSH&#34;</span>; <span style="color:#66d9ef">byte</span>[] data = hashAlgorithm.ComputeHash( Encoding.UTF8.GetBytes(input) ); Console.WriteLine(BitConverter.ToString(data).Replace(<span style="color:#e6db74">&#34;-&#34;</span>, String.Empty)); } </code></pre></div><p>Run the above 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">dotnet script run ./sha256hash.csx <span style="color:#75715e"># or simply ./sha256hash.csx</span> </code></pre></div><p>Output:</p> <pre tabindex="0"><code class="language-output" data-lang="output">1C29A2300A8A996F6760707E210DBD61B1C93AB74F86EE137B2EDEB6016E8793 </code></pre><h2 id="reference-links-">Reference links πŸ“–</h2> <p><a href="https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha256?view=netcore-3.1#constructors">System.Security.CryptoGraphy Class</a></p> <p><a href="https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.hashalgorithm?view=netcore-3.1">HashAlgorithm Base Class</a></p> <p><a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement">using statement reference in C#</a></p> <p><a href="https://www.c-sharpcorner.com/article/c-sharp-string-to-byte-array/">C# String To Byte Array</a></p> <p><a href="https://www.c-sharpcorner.com/article/how-to-convert-a-byte-array-to-a-string/">Convert Byte Array To String In C#</a></p> ARM templates - iterate & deploy resource https://dexterposh.github.io/posts/006-arm-template-loop/ Tue, 26 May 2020 15:18:35 +0530 https://dexterposh.github.io/posts/006-arm-template-loop/ <h2 id="background-">Background 🧐</h2> <p>I like ARM templates, I use it a lot to deploy Azure cloud resources but as all things it has some pain points associated with it. In this post, let&rsquo;s see how you can iterate over based on certain logic and deploy multiple resources using linked templates.</p> <p>As it stands out this logic of iterating over and deploying multiple instances of a resource tripped me a lot in the beginning.</p> <h2 id="walkthrough-">Walkthrough πŸƒ</h2> <p>Let&rsquo;s work through the whole process of writing an ARM template which deploys multiple resources.</p> <p>Github Repository - <a href="https://github.com/DexterPOSH/ArmTemplateLoopExample">ArmTemplateLoopExample</a></p> <blockquote> <p>This is a simple post demonstrating looping logic I often use, feel free to sprinkle your own best practices &amp; modifications on top e.g. storing templates in a private Cloud blob container, adding more parameters, names etc.</p> </blockquote> <h3 id="scenario">Scenario</h3> <p>Let&rsquo;s take a scenario of deploying many storage accounts based on the user input.</p> <p>Ideally, if you&rsquo;re in this situation you should write 2 templates and utilize ARM linked templates to deploy them because it becomes too cumbersome to maintain a single ARM template to deploy a resource and loop over user-input and deploy multiple iterations of that resource. Trust me this is coming from experience πŸ˜‰</p> <p>So, let&rsquo;s start by creating 2 templates, I am going to use GitHub repository here for storing those but you can use a Cloud blob store account as well.</p> <p>Below is how my project directory layout looks like.</p> <pre tabindex="0"><code class="language-output" data-lang="output">. β”œβ”€β”€ azuredeploy.json └── linkedTemplate └── storageaccount.json </code></pre><h3 id="author-linked-template">Author linked template</h3> <p>First thing to do when you&rsquo;re writing an ARM template is to make sure you understand that component properly, how it works, best practices while using that Azure component etc. Why? you might be wondering because ARM templates is how you deploy your Azure cloud infrastructure and it would be as good as you make your ARM templates, they&rsquo;re called blueprints for your Azure resources for this reason.</p> <p>But at the same time start small and head over to <a href="https://github.com/Azure/azure-quickstart-templates">azure-quickstart-templates</a> repository to get some samples.</p> <p>I found out that the template stored here in this <a href="https://github.com/Azure/azure-quickstart-templates/blob/master/101-storage-account-create/azuredeploy.json">101-storage-account-create</a> example is good enough for me. So, let me copypasta ✍ this and place the content inside my <code>linkedtemplate\storageaccount.json</code> file.</p> <p>So, we have a starting point which can deploy a single storage account for us, but you would notice on closer inspection that this <code>storageaccount.json</code> template doesn&rsquo;t take storageAccountName as a parameter but generates it in the variables section.</p> <p>Let&rsquo;s quickly modify it. Changes made:</p> <ul> <li>add parameter <code>storageAccountName</code></li> <li>remove variable <code>storageAccountName</code></li> <li>change <code>[variables('storageAccountName')]</code> references to <code>[parameters('storageAccountName')]</code></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-json" data-lang="json">{ <span style="color:#f92672">&#34;$schema&#34;</span>: <span style="color:#e6db74">&#34;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&#34;</span>, <span style="color:#f92672">&#34;contentVersion&#34;</span>: <span style="color:#e6db74">&#34;1.0.0.0&#34;</span>, <span style="color:#f92672">&#34;parameters&#34;</span>: { <span style="color:#f92672">&#34;storageAccountName&#34;</span>: { <span style="color:#960050;background-color:#1e0010">//</span> <span style="color:#960050;background-color:#1e0010">added</span> <span style="color:#960050;background-color:#1e0010">the</span> <span style="color:#960050;background-color:#1e0010">storageAccountName</span> <span style="color:#960050;background-color:#1e0010">parameter</span> <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span> }, <span style="color:#f92672">&#34;storageAccountType&#34;</span>: { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, <span style="color:#f92672">&#34;defaultValue&#34;</span>: <span style="color:#e6db74">&#34;Standard_LRS&#34;</span>, <span style="color:#f92672">&#34;allowedValues&#34;</span>: [ <span style="color:#e6db74">&#34;Standard_LRS&#34;</span>, <span style="color:#e6db74">&#34;Standard_GRS&#34;</span>, <span style="color:#e6db74">&#34;Standard_ZRS&#34;</span>, <span style="color:#e6db74">&#34;Premium_LRS&#34;</span> ], <span style="color:#f92672">&#34;metadata&#34;</span>: { <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;Storage Account type&#34;</span> } }, <span style="color:#f92672">&#34;location&#34;</span>: { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, <span style="color:#f92672">&#34;defaultValue&#34;</span>: <span style="color:#e6db74">&#34;[resourceGroup().location]&#34;</span>, <span style="color:#f92672">&#34;metadata&#34;</span>: { <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;Location for all resources.&#34;</span> } } }, <span style="color:#f92672">&#34;variables&#34;</span>: { <span style="color:#960050;background-color:#1e0010">//</span> <span style="color:#960050;background-color:#1e0010">removed</span> <span style="color:#960050;background-color:#1e0010">the</span> <span style="color:#960050;background-color:#1e0010">storageAccountName</span> <span style="color:#960050;background-color:#1e0010">variable</span> <span style="color:#960050;background-color:#1e0010">from</span> <span style="color:#960050;background-color:#1e0010">here</span> }, <span style="color:#f92672">&#34;resources&#34;</span>: [ { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;Microsoft.Storage/storageAccounts&#34;</span>, <span style="color:#f92672">&#34;apiVersion&#34;</span>: <span style="color:#e6db74">&#34;2019-04-01&#34;</span>, <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;[parameters(&#39;storageAccountName&#39;)]&#34;</span>, <span style="color:#f92672">&#34;location&#34;</span>: <span style="color:#e6db74">&#34;[parameters(&#39;location&#39;)]&#34;</span>, <span style="color:#f92672">&#34;sku&#34;</span>: { <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;[parameters(&#39;storageAccountType&#39;)]&#34;</span> }, <span style="color:#f92672">&#34;kind&#34;</span>: <span style="color:#e6db74">&#34;StorageV2&#34;</span>, <span style="color:#f92672">&#34;properties&#34;</span>: {} } ], <span style="color:#f92672">&#34;outputs&#34;</span>: { <span style="color:#f92672">&#34;storageAccountName&#34;</span>: { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;[parameters(&#39;storageAccountName&#39;)]&#34;</span> } } } </code></pre></div><h3 id="author-the-stitching-logic">Author the stitching logic</h3> <p>Moving on to the logic of consolidating user input and then looping over and deploying a storage account multiple times.</p> <p>The gist is that we have to do below:</p> <ul> <li> <p>Use variable iteration to create an array of objects based on our <code>numberofStorageAccounts</code> parameter value</p> </li> <li> <p>Use resource iteration later with a linked template deployment and index into the array created above for parameter values.</p> </li> </ul> <blockquote> <p>Don&rsquo;t worry if this is a bit daunting. It was for me the first time.</p> </blockquote> <h4 id="adding-barebone-template">Adding barebone template</h4> <p>Let&rsquo;s start by creating a blank ARM template. Open the <code>azuredeploy.json</code> in VSCode. Key in <code>arm</code> and it would give you a snippet dropdown, select the first one for targeting a Resource group deployment.</p> <p><img src="https://dexterposh.github.io/static/006/arm_snippets.png" alt="alt"></p> <p>So, we get 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-json" data-lang="json">{ <span style="color:#f92672">&#34;$schema&#34;</span>: <span style="color:#e6db74">&#34;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&#34;</span>, <span style="color:#f92672">&#34;contentVersion&#34;</span>: <span style="color:#e6db74">&#34;1.0.0.0&#34;</span>, <span style="color:#f92672">&#34;parameters&#34;</span>: {}, <span style="color:#f92672">&#34;functions&#34;</span>: [], <span style="color:#f92672">&#34;variables&#34;</span>: {}, <span style="color:#f92672">&#34;resources&#34;</span>: [], <span style="color:#f92672">&#34;outputs&#34;</span>: {} } </code></pre></div><h5 id="add-parameters">Add parameters</h5> <p>Time to add in some parameters to our <code>azuredeploy.json</code> which is end-user facing. So you need to take input in this one from the user (which could be yourself as well) and then pass those over to the linkedtemplate.</p> <ul> <li>storageAccountNamePrefix - prefix for the storage accounts to be deployed. Length 5-10.</li> <li>numberofStorageAccounts - integer representing how many storage accounts to deploy. [Default - 1, Min 1, Max 10.</li> <li>storageAccountType - Type of the storage accounts, predefined allowed values. Default - Standard_GRS.</li> <li>location - location for the storage accounts. Default is RG location.</li> </ul> <p>Below is how the <code>parameters</code> object looks now.</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="color:#e6db74">&#34;parameters&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> { <span style="color:#f92672">&#34;storageAccountNamePrefix&#34;</span>: { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, <span style="color:#f92672">&#34;minLength&#34;</span>: <span style="color:#e6db74">&#34;5&#34;</span>, <span style="color:#f92672">&#34;maxLength&#34;</span>: <span style="color:#e6db74">&#34;10&#34;</span> }, <span style="color:#f92672">&#34;numberofStorageAccounts&#34;</span>: { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;int&#34;</span>, <span style="color:#f92672">&#34;defaultValue&#34;</span>: <span style="color:#ae81ff">1</span>, <span style="color:#f92672">&#34;minValue&#34;</span>: <span style="color:#e6db74">&#34;1&#34;</span>, <span style="color:#f92672">&#34;maxValue&#34;</span>: <span style="color:#e6db74">&#34;10&#34;</span> }, <span style="color:#f92672">&#34;storageAccountType&#34;</span>: { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, <span style="color:#f92672">&#34;defaultValue&#34;</span>: <span style="color:#e6db74">&#34;Standard_GRS&#34;</span>, <span style="color:#f92672">&#34;allowedValues&#34;</span>: [ <span style="color:#e6db74">&#34;Standard_LRS&#34;</span>, <span style="color:#e6db74">&#34;Standard_GRS&#34;</span>, <span style="color:#e6db74">&#34;Standard_ZRS&#34;</span>, <span style="color:#e6db74">&#34;Premium_LRS&#34;</span> ] }, <span style="color:#f92672">&#34;location&#34;</span>: { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;string&#34;</span>, <span style="color:#f92672">&#34;defaultValue&#34;</span>: <span style="color:#e6db74">&#34;[resourceGroup().location]&#34;</span>, <span style="color:#f92672">&#34;metadata&#34;</span>: { <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;Location for all resources.&#34;</span> } } } </code></pre></div><h5 id="add-the-variables-iteration-logic">Add the variables (iteration logic)</h5> <p>I typically like to use variables a lot for transforming the input parameters and then using these variables later in the resources because it makes it easier in future to just modify these variables at once place.</p> <p>Use the concept of <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/copy-variables">variable iteration</a> in ARM templates.</p> <p>We use the above concept to do the below</p> <ul> <li>Create a variable named <code>_deployMultipleStorageAccounts</code> (sort of a convention I follow to name these variables used later in deployment to preceed with <code>_deploy</code>)</li> <li>Use the parameter <code>numberofStorageAccounts</code> to loop over that many times</li> <li>Use the <code>input</code> property in the copy loop object to generate an object containing properties which will be mapped one to one with the linked template storage.</li> </ul> <p>I prefer one to one mapping between the properties inside the <code>input</code> in the copy loop to the parameters of the linked template. It makes it easier to index into them and specify them (you&rsquo;ll see later).</p> <p>Below is a gist of what I added in the variables property.</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="color:#e6db74">&#34;variables&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> { <span style="color:#f92672">&#34;copy&#34;</span>: [ { <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;_deployMultipleStorageAccounts&#34;</span>, <span style="color:#960050;background-color:#1e0010">//</span> <span style="color:#960050;background-color:#1e0010">variable</span> <span style="color:#960050;background-color:#1e0010">name</span> <span style="color:#960050;background-color:#1e0010">used</span> <span style="color:#960050;background-color:#1e0010">later</span> <span style="color:#960050;background-color:#1e0010">with</span> <span style="color:#960050;background-color:#1e0010">resources</span> <span style="color:#f92672">&#34;count&#34;</span>: <span style="color:#e6db74">&#34;[parameters(&#39;numberofStorageAccounts&#39;)]&#34;</span>, <span style="color:#960050;background-color:#1e0010">//</span> <span style="color:#960050;background-color:#1e0010">loop</span> <span style="color:#960050;background-color:#1e0010">over</span> <span style="color:#960050;background-color:#1e0010">numberofStorageAccounts</span> <span style="color:#960050;background-color:#1e0010">time</span> <span style="color:#f92672">&#34;input&#34;</span>: { <span style="color:#960050;background-color:#1e0010">//</span> <span style="color:#960050;background-color:#1e0010">input</span> <span style="color:#960050;background-color:#1e0010">object</span> <span style="color:#960050;background-color:#1e0010">con</span> <span style="color:#f92672">&#34;storageAccountType&#34;</span>: <span style="color:#e6db74">&#34;[parameters(&#39;storageAccountType&#39;)]&#34;</span>, <span style="color:#f92672">&#34;location&#34;</span>: <span style="color:#e6db74">&#34;[parameters(&#39;location&#39;)]&#34;</span>, <span style="color:#f92672">&#34;storageAccountName&#34;</span>: <span style="color:#e6db74">&#34;[ </span><span style="color:#e6db74"> concat( </span><span style="color:#e6db74"> parameters(&#39;storageAccountNamePrefix&#39;), </span><span style="color:#e6db74"> uniqueString(resourceGroup().id), </span><span style="color:#e6db74"> copyIndex(&#39;_deployMultipleStorageAccounts&#39;) </span><span style="color:#e6db74"> ) </span><span style="color:#e6db74"> ]&#34;</span> } } ] } </code></pre></div><p>In the outputs section we added a <code>variables</code> property which essentially displays the value for the variable <code>_deployMultipleStorageAccounts</code>. This can be used later with a trick to see what values go inside this 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-json" data-lang="json"><span style="color:#e6db74">&#34;outputs&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> { <span style="color:#f92672">&#34;variables&#34;</span>: { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;object&#34;</span>, <span style="color:#f92672">&#34;value&#34;</span>: { <span style="color:#f92672">&#34;_deployMultipleStorageAccounts&#34;</span>: <span style="color:#e6db74">&#34;[variables(&#39;_deployMultipleStorageAccounts&#39;)]&#34;</span> } } } </code></pre></div><p>Also, I added a <code>variables</code> property in the output which is used to display what goes inside this variable once it is run by ARM API.</p> <p><img src="https://dexterposh.github.io/static/006/transformParams.png" alt="alt"></p> <p>From a data-view point above creates a variable named <code>_deployMultipleStorageAccounts</code> which is an array of Json objects.</p> <p>If we assume the <code>parameters('numberofStorageAccounts')</code> is 2, <code>parameters('storageAccountType')</code> is <em>Standard_GRS</em> and <code>parameters('location')</code> is <em>SouthEastAsia</em>, then it creates an array like below:</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="color:#f92672">&#34;storageAccountName&#34;</span>: <span style="color:#e6db74">&#34;&lt;generatedValuebyARM&gt;&#34;</span>, <span style="color:#f92672">&#34;storageAccountType&#34;</span>: <span style="color:#e6db74">&#34;Standard_GRS&#34;</span>, <span style="color:#f92672">&#34;loation&#34;</span>: <span style="color:#e6db74">&#34;SouthEastAsia&#34;</span>, }, { <span style="color:#f92672">&#34;storageAccountName&#34;</span>: <span style="color:#e6db74">&#34;&lt;generatedValuebyARM&gt;&#34;</span>, <span style="color:#f92672">&#34;storageAccountType&#34;</span>: <span style="color:#e6db74">&#34;Standard_GRS&#34;</span>, <span style="color:#f92672">&#34;location&#34;</span>: <span style="color:#e6db74">&#34;SouthEastAsia&#34;</span>, } ] </code></pre></div><h4 id="add-the-deployment-resource">Add the deployment resource</h4> <p>Now, you already know we have the linked template to deploy a single storage account. So, we just need to invoke/call that template multiple times and pass in paramters.</p> <p>This is done by an ARM template technique called as ARM template linked templates. Read about it more <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates#linked-template">here</a></p> <blockquote> <p>Follow a <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-tutorial-linked-template?tabs=azure-powershell#create-a-linked-template">Tutorial</a> to deploy a linked template, if this is the firs time you&rsquo;re hearing about this.</p> </blockquote> <p>In short, within our <code>azuredeploy.json</code> template we need to use a resource of type <code>Microsoft.Resources/deployments</code> to link to our <code>storageaccount.json</code> template and inside this resource we need to use another concept termed as <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/copy-resources"><em>Resource iteration</em></a></p> <p>Let&rsquo;s add it.</p> <p>This is how my resources array property looks like.</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="color:#e6db74">&#34;resources&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> [ { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;Microsoft.Resources/deployments&#34;</span>, <span style="color:#f92672">&#34;apiVersion&#34;</span>: <span style="color:#e6db74">&#34;2019-10-01&#34;</span>, <span style="color:#f92672">&#34;condition&#34;</span>: <span style="color:#66d9ef">false</span>, <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;[concat(&#39;deploy-linkedStorageTemplate&#39;, copyIndex())]&#34;</span>, <span style="color:#f92672">&#34;copy&#34;</span>: { <span style="color:#f92672">&#34;count&#34;</span>: <span style="color:#e6db74">&#34;[parameters(&#39;numberofStorageAccounts&#39;)]&#34;</span>, <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;_loopToDeployStorageAccounts&#34;</span>, <span style="color:#f92672">&#34;mode&#34;</span>: <span style="color:#e6db74">&#34;Parallel&#34;</span> }, <span style="color:#f92672">&#34;properties&#34;</span>: { <span style="color:#f92672">&#34;mode&#34;</span>: <span style="color:#e6db74">&#34;Incremental&#34;</span>, <span style="color:#f92672">&#34;templateLink&#34;</span>: { <span style="color:#f92672">&#34;contentVersion&#34;</span>: <span style="color:#e6db74">&#34;1.0.0.0&#34;</span>, <span style="color:#f92672">&#34;uri&#34;</span>: <span style="color:#e6db74">&#34;https://raw.githubusercontent.com/DexterPOSH/ArmTemplateLoopExample/master/linkedtemplate/storageaccount.json&#34;</span> }, <span style="color:#f92672">&#34;parameters&#34;</span>: { <span style="color:#f92672">&#34;storageAccountName&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;[variables(&#39;_deployMultipleStorageAccounts&#39;)[copyIndex()].storageAccountName]&#34;</span> }, <span style="color:#f92672">&#34;storageAccountType&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;[variables(&#39;_deployMultipleStorageAccounts&#39;)[copyIndex()].storageAccountType]&#34;</span> }, <span style="color:#f92672">&#34;location&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;[variables(&#39;_deployMultipleStorageAccounts&#39;)[copyIndex()].location]&#34;</span> } } } } ] </code></pre></div><p>I know it&rsquo;s a handful but below is a breakdown of major things it does.</p> <ul> <li>Uses <code>Microsoft.Resources/deployments</code> resource type to deploy another template.</li> </ul> <blockquote> <p>Note we generate a unique name for the deployment by concating the <code>copyIndex()</code> The <code>condition</code> property is set to <code>false</code></p> </blockquote> <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="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;Microsoft.Resources/deployments&#34;</span>, <span style="color:#f92672">&#34;apiVersion&#34;</span>: <span style="color:#e6db74">&#34;2019-10-01&#34;</span>, <span style="color:#f92672">&#34;condition&#34;</span>: <span style="color:#66d9ef">false</span>, <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;[concat(&#39;deploy-linkedStorageTemplate&#39;, copyIndex())]&#34;</span> <span style="color:#960050;background-color:#1e0010">//&lt;--</span> <span style="color:#960050;background-color:#1e0010">skipped</span> <span style="color:#960050;background-color:#1e0010">below</span> <span style="color:#960050;background-color:#1e0010">properties</span> <span style="color:#960050;background-color:#1e0010">--&gt;</span> } </code></pre></div><ul> <li>Uses the <code>uri</code> of the raw template link for the <code>storageaccount.json</code> in the GitHub repository.</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-json" data-lang="json"><span style="color:#e6db74">&#34;templateLink&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> { <span style="color:#f92672">&#34;contentVersion&#34;</span>: <span style="color:#e6db74">&#34;1.0.0.0&#34;</span>, <span style="color:#f92672">&#34;uri&#34;</span>: <span style="color:#e6db74">&#34;https://raw.githubusercontent.com/DexterPOSH/ArmTemplateLoopExample/master/linkedtemplate/storageaccount.json&#34;</span> } </code></pre></div><ul> <li>Uses resource iteration by using <code>copy</code> property and using the <code>parameters('numberofStorageAccounts')</code> as the value for count, which means it loops over this resource this many times. Also, gives this copy loop a friendly name <code>_loopToDeployStorageAccounts</code>.</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-json" data-lang="json"><span style="color:#e6db74">&#34;copy&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> { <span style="color:#f92672">&#34;count&#34;</span>: <span style="color:#e6db74">&#34;[parameters(&#39;numberofStorageAccounts&#39;)]&#34;</span>, <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;_loopToDeployStorageAccounts&#34;</span> } </code></pre></div><ul> <li>Passes on the parameters to this linked template by indexing into the variable <code>_deployMultipleStorageAccounts</code></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-json" data-lang="json"><span style="color:#e6db74">&#34;storageAccountName&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;[variables(&#39;_deployMultipleStorageAccounts&#39;)[copyIndex()].storageAccountName]&#34;</span> } </code></pre></div><h4 id="dry-run---verify-variable-iteration-logic">Dry run - Verify Variable iteration logic</h4> <p>Based on my experience with this approach of looping over, we can most of the time validate what is inside the variable created for looping to verify it will work.</p> <p>Remember the <code>condition</code> is set to <code>false</code> for our linked template deployment resource, which means when we submit this ARM template for deployment it won&rsquo;t trigger it but however the <code>azuredeploy.json</code> will be processed and we will get the output back which contains the <code>variables</code> property.</p> <p>Let&rsquo;s create an ARM template parameters file, with the new release of the ARM tools VSCode extension, it is natively possible to generate these parameters file. Read more in the release notes <a href="https://marketplace.visualstudio.com/items?itemName=msazurermtools.azurerm-vscode-tools">here</a>.</p> <p>Click on <em>Select Parameter File&hellip;</em> (at the bottom) &gt; <em>New</em> &gt; <em>All parameters</em> &gt; Save it. Open it and fill the values.</p> <p><img src="https://dexterposh.github.io/static/006/generateParam.png" alt="Generate ARM parameters file"></p> <p>This is how it looks after adding values.</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="color:#f92672">&#34;$schema&#34;</span>: <span style="color:#e6db74">&#34;https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#&#34;</span>, <span style="color:#f92672">&#34;contentVersion&#34;</span>: <span style="color:#e6db74">&#34;1.0.0.0&#34;</span>, <span style="color:#f92672">&#34;parameters&#34;</span>: { <span style="color:#f92672">&#34;storageAccountNamePrefix&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;azurearm6754&#34;</span> }, <span style="color:#f92672">&#34;numberofStorageAccounts&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#ae81ff">2</span> }, <span style="color:#f92672">&#34;storageAccountType&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;Standard_GRS&#34;</span> }, <span style="color:#f92672">&#34;location&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;southindia&#34;</span> } } } </code></pre></div><p>Deploy it using Az PowerShell module cmdelt</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-powershell" data-lang="powershell">$rg = <span style="color:#e6db74">&#34;test_arm_rg&#34;</span> New-AzResourceGroupDeployment -TemplateFile ./azuredeploy.json -TemplateParameterFile ./azuredeploy.parameters.json -ResourceGroupName $rg </code></pre></div><pre tabindex="0"><code class="language-output" data-lang="output">DeploymentName : azuredeploy ResourceGroupName : test_arm_rg ProvisioningState : Succeeded Timestamp : 06/07/2020 14:24:21 Mode : Incremental TemplateLink : Parameters : Name Type Value ========================== ========================= ========== storageAccountNamePrefix String azurearm6754 numberofStorageAccounts Int 2 storageAccountType String Standard_GRS location String southindia Outputs : Name Type Value =============== ========================= ========== variables Object { &quot;_deployMultipleStorageAccounts&quot;: [ { &quot;storageAccountType&quot;: &quot;Standard_GRS&quot;, &quot;location&quot;: &quot;southindia&quot;, &quot;storageAccountName&quot;: &quot;azurearm67543ub5zsu77klvq0&quot; }, { &quot;storageAccountType&quot;: &quot;Standard_GRS&quot;, &quot;location&quot;: &quot;southindia&quot;, &quot;storageAccountName&quot;: &quot;azurearm67543ub5zsu77klvq1&quot; } ] } DeploymentDebugLogLevel : </code></pre><p>Look at the outputs section, it clearly lists out the variable we generated and upon which our whole logic of depolying multiple resources existed.</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="color:#e6db74">&#34;_deployMultipleStorageAccounts&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> [ { <span style="color:#f92672">&#34;storageAccountType&#34;</span>: <span style="color:#e6db74">&#34;Standard_GRS&#34;</span>, <span style="color:#f92672">&#34;location&#34;</span>: <span style="color:#e6db74">&#34;southindia&#34;</span>, <span style="color:#f92672">&#34;storageAccountName&#34;</span>: <span style="color:#e6db74">&#34;azurearm67543ub5zsu77klvq0&#34;</span> }, { <span style="color:#f92672">&#34;storageAccountType&#34;</span>: <span style="color:#e6db74">&#34;Standard_GRS&#34;</span>, <span style="color:#f92672">&#34;location&#34;</span>: <span style="color:#e6db74">&#34;southindia&#34;</span>, <span style="color:#f92672">&#34;storageAccountName&#34;</span>: <span style="color:#e6db74">&#34;azurearm67543ub5zsu77klvq1&#34;</span> } ] </code></pre></div><h3 id="test-the-solution">Test the solution</h3> <p>Once the variable iteration logic is verified, it is time to deploy the tempalte to see that it actually creates.</p> <p>Wait! before you jump into testing it you need to make a minor change. Can you guess what? Set <code>condition</code> to <code>true</code> inside the linked template deployment resource.</p> <p>Below is a snippet of where that change goes inside the 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-json" data-lang="json"><span style="color:#e6db74">&#34;resources&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> [ { <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;Microsoft.Resources/deployments&#34;</span>, <span style="color:#f92672">&#34;apiVersion&#34;</span>: <span style="color:#e6db74">&#34;2019-10-01&#34;</span>, <span style="color:#f92672">&#34;condition&#34;</span>: <span style="color:#66d9ef">true</span> <span style="color:#960050;background-color:#1e0010">//</span> <span style="color:#960050;background-color:#1e0010">set</span> <span style="color:#960050;background-color:#1e0010">this</span> <span style="color:#960050;background-color:#1e0010">to</span> <span style="color:#66d9ef">true</span> <span style="color:#960050;background-color:#1e0010">to</span> <span style="color:#960050;background-color:#1e0010">actually</span> <span style="color:#960050;background-color:#1e0010">deploy</span> <span style="color:#960050;background-color:#1e0010">the</span> <span style="color:#960050;background-color:#1e0010">linked</span> <span style="color:#960050;background-color:#1e0010">template</span>, <span style="color:#960050;background-color:#1e0010">//&lt;--</span> <span style="color:#960050;background-color:#1e0010">skipped</span> <span style="color:#960050;background-color:#1e0010">below</span> <span style="color:#960050;background-color:#1e0010">properties</span> <span style="color:#960050;background-color:#1e0010">--&gt;</span> } ] </code></pre></div><p>Deploy again, this time it should deploy multiple storage accounts.</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-powershell" data-lang="powershell">$rg = <span style="color:#e6db74">&#34;test_arm_rg&#34;</span> New-AzResourceGroupDeployment -TemplateFile ./azuredeploy.json -TemplateParameterFile ./azuredeploy.parameters.json -ResourceGroupName $rg </code></pre></div><h2 id="tldr-solution-">TLDR; Solution πŸ—ž</h2> <p>Head over to this GitHub repository to see the ARM templates.</p> <p><a href="https://github.com/DexterPOSH/ArmTemplateLoopExample">ArmTemplateLoopExample</a></p> <h2 id="references-">References πŸ“š</h2> <p><a href="https://marketplace.visualstudio.com/items?itemName=msazurermtools.azurerm-vscode-tools">Azure Resource Manager (ARM) Tools VSCode extension</a></p> <p><a href="https://github.com/Azure/azure-quickstart-templates">Azure QuickStart Templates</a></p> <p><a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates#linked-template">Linked Templates</a></p> <p><a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-tutorial-linked-template?tabs=azure-powershell#create-a-linked-template">Deploy a linked template tutorial</a></p> <p><a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/copy-resources">Resource-Ieration</a></p> AKS PowerShell Tip - Add Authorized Ip https://dexterposh.github.io/posts/005-aks-tip-ip/ Fri, 01 May 2020 12:21:28 +0530 https://dexterposh.github.io/posts/005-aks-tip-ip/ <h2 id="background-">Background 🐼</h2> <p>Recently, I found out that there is no sane way to perform adding a public IP address to the authorized IP address ranges using either the <a href="https://docs.microsoft.com/en-us/azure/aks/api-server-authorized-ip-ranges#update-a-clusters-api-server-authorized-ip-ranges">Az CLI</a> or <a href="https://docs.microsoft.com/en-us/powershell/module/az.aks/?view=azps-3.8.0">Az.Aks</a> PowerShell (no cmdlets available yet) module.</p> <p>From the official docs it says to use something like below format with Az CLI.</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">az aks update <span style="color:#ae81ff">\ </span><span style="color:#ae81ff"></span> --resource-group myResourceGroup <span style="color:#ae81ff">\ </span><span style="color:#ae81ff"></span> --name myAKSCluster <span style="color:#ae81ff">\ </span><span style="color:#ae81ff"></span> --api-server-authorized-ip-ranges 73.140.245.0/24 </code></pre></div><p>But it doesn&rsquo;t tell you how to append the IP to the range, instead you need to supply a comma separated value of public IP addresses.</p> <h2 id="challenge-">Challenge ☁️</h2> <p>Well, this is can be done by using Az CLI with PowerShell or Bash and parsing output then generating a comma separated string and passing it back to Az CLI 😞</p> <h2 id="solution-">Solution ⚑</h2> <p>Often, when I am hit with such limitations with cmdlets or Az CLI making life hard. I go back to using simply the 2 cmdlets provided by <em>Az.Resources</em> module.</p> <p>Behold mighty!</p> <ul> <li><em>Get-AzResource</em> - Gets the Az resource</li> <li><em>Set-AzResource</em> - Modifies the Az resource</li> </ul> <p>I ended up doing the below and creating a utility function out of it.</p> <p>First, get the AKS Cluster resource. Make sure to specify the <strong>-ExpandProperties</strong> switch to get back full fledged resource otherwise it returns a shallow instance.</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-powershell" data-lang="powershell">$ResourceGroup = <span style="color:#e6db74">&#34;test-aks-rg&#34;</span> $Name = <span style="color:#e6db74">&#34;aksCluster001&#34;</span> $IP = <span style="color:#e6db74">&#34;110.91.234.43&#34;</span> $AksCluster = Get-AzResource -ResourceGroupName $ResourceGroup -Name $Name -ExpandProperties -ErrorAction Stop </code></pre></div><p>Once you have the resource, walk-through the properties and append the IP (+= operator in PowerShell) to the local copy of the resource.</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-powershell" data-lang="powershell">$orgClusterInfo.Properties.apiServerAccessProfile.authorizedIpRanges += $Ip </code></pre></div><p>Finally, perform a Set operation by piping the modified local resource copy to <strong>Set-AzResource</strong> cmdlet.</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-powershell" data-lang="powershell">$orgClusterInfo | Set-AzResource -ErrorAction Stop </code></pre></div><h2 id="takeaway-">Takeaway πŸ”₯</h2> <p>Even, when there are certain utility functions not available in the Az PowerShell module. We can rely on the <em>`</em>-Resource* cmdlets to work our way through.</p> Azure DevOps Tip - Job re-use within a Stage https://dexterposh.github.io/posts/004-azdo-tip-job/ Sun, 26 Apr 2020 09:53:50 +0530 https://dexterposh.github.io/posts/004-azdo-tip-job/ <h2 id="background">Background</h2> <p>Azure DevOps introduced multi-stage yaml pipelines a while ago. It allows us to define our entire Build/Release landscape inside these yaml definitions.</p> <p>To re-iterate of some terms used in this post:</p> <ul> <li>A pipeline comprises one or more stages</li> <li>Stage is collection of jobs</li> <li>Job runs on an agent/ agentless</li> <li>Job contains steps (task/script)</li> <li>Steps are the atomic unit to perform a task</li> </ul> <h2 id="challenge">Challenge</h2> <p>Recently, working our multi-stage yaml pipelines, we hit an interesting behavior with our job template re-use.</p> <p>We had quite few common steps we require to take in each stage (multiple times) to hit an external API. So, to re-use we extracted them out as a job <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops">template</a>.</p> <h3 id="jobtemplateyml">jobtemplate.yml</h3> <p>For this post, creating a sample job and taking an <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/delay?view=azure-devops">agentless delay task</a> as an example but this job template can include series of steps.</p> <p>Below is my extracted out job template definition (indues a delay) which I want to re-use within stages in my main <strong>azure-pipelines.yml</strong> pipeline definition later.</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="color:#f92672">parameters</span>: <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#39;windows&#39;</span> <span style="color:#f92672">jobs</span>: - <span style="color:#f92672">job</span>: <span style="color:#ae81ff">commonJob</span> <span style="color:#f92672">displayName</span>: <span style="color:#ae81ff">Common Job</span> <span style="color:#f92672">pool</span>: <span style="color:#ae81ff">server</span> <span style="color:#f92672">steps</span>: - <span style="color:#f92672">task</span>: <span style="color:#ae81ff">Delay@1</span> <span style="color:#f92672">displayName</span>: <span style="color:#e6db74">&#39;Delay by 1 minutes for ${{ parameters.name }}&#39;</span> <span style="color:#f92672">inputs</span>: <span style="color:#f92672">delayForMinutes</span>: <span style="color:#ae81ff">1</span> </code></pre></div><h3 id="azure-pipelinesyml">azure-pipelines.yml</h3> <p>Now, I want to call my Job template from above inside two stages Build &amp; Deploy in the multi-stage yaml pipeline as below.</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="color:#f92672">stages</span>: - <span style="color:#f92672">stage</span>: <span style="color:#ae81ff">Build</span> <span style="color:#f92672">jobs</span>: - <span style="color:#f92672">template</span>: <span style="color:#ae81ff">./jobtemplate.yml</span> - <span style="color:#f92672">job</span>: <span style="color:#ae81ff">Build1</span> - <span style="color:#f92672">stage</span>: <span style="color:#ae81ff">Deploy</span> <span style="color:#f92672">jobs</span>: - <span style="color:#f92672">template</span>: <span style="color:#ae81ff">./jobtemplate.yml</span> - <span style="color:#f92672">job</span>: <span style="color:#ae81ff">Deploy1</span> </code></pre></div><p>Kick off a build for this and you would see it in the AzDO portal, that it works out well.</p> <p><img src="https://dexterposh.github.io/static/004/jobWithinDiffStages.png" alt="Same Job template used with diffn stages"></p> <p>But, if I try to use the same Job template multiple times within the same stage that is when things get interesting. Let&rsquo;s modify our <strong>azure-pipelines.yml</strong> definition now.</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="color:#f92672">stages</span>: - <span style="color:#f92672">stage</span>: <span style="color:#ae81ff">Build</span> <span style="color:#f92672">jobs</span>: - <span style="color:#f92672">template</span>: <span style="color:#ae81ff">./jobtemplate.yml</span> <span style="color:#f92672">parameters</span>: <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Deepak</span> - <span style="color:#f92672">template</span>: <span style="color:#ae81ff">./jobtemplate.yml</span> <span style="color:#f92672">parameters</span>: <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Dhami</span> - <span style="color:#f92672">job</span>: <span style="color:#ae81ff">Build1</span> - <span style="color:#f92672">stage</span>: <span style="color:#ae81ff">Deploy</span> <span style="color:#f92672">jobs</span>: - <span style="color:#f92672">template</span>: <span style="color:#ae81ff">./jobtemplate.yml</span> - <span style="color:#f92672">job</span>: <span style="color:#ae81ff">Deploy1</span> </code></pre></div><p>Try running this new modified pipeline and you would be greeted with the below in the portal.</p> <p><img src="https://dexterposh.github.io/static/004/nameCollision.png" alt="Job name not unique"></p> <p>This is quite obvious but the job names in a stage should be unique.</p> <h2 id="solution">Solution</h2> <p>To get to a solution we looked at generating a unique name for our jobs but it turns out there is a very simple solution to this problem.</p> <p>One can remove the job identifier in the job template completely while extracting out jobs to re-use.</p> <p>What this means is we had to modify our Job templates like below:</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="color:#f92672">parameters</span>: <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#39;windows&#39;</span> <span style="color:#f92672">jobs</span>: - <span style="color:#f92672">job</span>: <span style="color:#75715e"># Identifier here is removed</span> <span style="color:#f92672">displayName</span>: <span style="color:#ae81ff">Common Job</span> <span style="color:#f92672">pool</span>: <span style="color:#ae81ff">server</span> <span style="color:#f92672">steps</span>: - <span style="color:#f92672">task</span>: <span style="color:#ae81ff">Delay@1</span> <span style="color:#f92672">displayName</span>: <span style="color:#e6db74">&#39;Delay by 1 minutes for ${{ parameters.name }}&#39;</span> <span style="color:#f92672">inputs</span>: <span style="color:#f92672">delayForMinutes</span>: <span style="color:#ae81ff">1</span> </code></pre></div><p>Now, it works.</p> <p><img src="https://dexterposh.github.io/static/004/jobReuse.png" alt="Job re-use within same stage"></p> <h2 id="conclusion">Conclusion</h2> <p>As a general practice, when I start extracting out steps as independent job templates I tend to not populate the Job identifier if it&rsquo;s scope is to be re-used multiple times in the same stage.</p> <p>But if there is a job which you want to ensure that it is allowed to run only once, you could stick with populating the field.</p> <p>So, as every engineer building solution says:</p> <blockquote> <p>It depends on the use-case</p> </blockquote> Azure DevOps Tip - Find private APIs https://dexterposh.github.io/posts/002-azdo-tip-api/ Tue, 14 Apr 2020 15:06:22 +0530 https://dexterposh.github.io/posts/002-azdo-tip-api/ <h2 id="problem">Problem</h2> <p>Often working with Azure DevOps, I hit a wall trying to automate some tasks but there are no REST APIs made public yet.</p> <p>It was one of those task of automating creation of Environments in multi-stage YAML based pipelines in AzDO.</p> <p><img src="https://dexterposh.github.io/static/002/env.png" alt="Azure DevOps environments"></p> <p>Quick research reveals that this has been requested in <a href="https://developercommunity.visualstudio.com/content/problem/820737/rest-apis-for-environments-and-its-resources-multi.html">uservoice</a> (please upvote). Let&rsquo;s see one of the very simple ways to discover some of these APIs.</p> <h2 id="developers-tools-to-rescue">Developers Tools to rescue</h2> <p>Using your browser&rsquo;s developers tools you can actually inspect the HTTP requests being made while performing an action in the web portal.</p> <p>Let&rsquo;s do this.</p> <p><img src="https://dexterposh.github.io/static/002/devnetwork.png" alt="Developer tools"></p> <p>Let&rsquo;s click on the &ldquo;Create Environment&rdquo; button, fill out some dummy values, hit create and keep an eye on the network tab in the developer tools.</p> <p><img src="https://dexterposh.github.io/static/002/envcreatenetwork.png" alt="Create env watch network"></p> <p>We see some activity, it might take you some time to walk through what happened but in this case the top activity named &ldquo;environments&rdquo; has the required details.</p> <p>See below and note the URL &amp; method used:</p> <p><img src="https://dexterposh.github.io/static/002/analyzerequest.png" alt="Analyze request sent"></p> <p>Also, make note of the Json request in the payload.</p> <p><img src="https://dexterposh.github.io/static/002/requestpayload.png" alt="Request payload"></p> <p>That&rsquo;s mostly it, fire up postman/PowerShell to make the API call to test this.</p> <h2 id="invoke-restmethod-in-pwsh">Invoke-RestMethod in Pwsh</h2> <p><a href="https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;tabs=preview-page">Generate a Personal Access Token</a> in AzDO, typically start with a short lived PAT token with full access and then nail down on the specific permissions you need.</p> <p>Below is the code snippet, I used with AzDO to hit the REST API endpoint:</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-powershell" data-lang="powershell">$url = <span style="color:#e6db74">&#39;https://dev.azure.com/ddhami/BITPro.AzDeploy/_apis/distributedtask/environments&#39;</span> $cred = Get-Credential -UserName <span style="color:#e6db74">&#39;vsts&#39;</span> -Message <span style="color:#e6db74">&#39;Enter AzDO Personal Access Token with privs to create env&#39;</span> $encodedValue = <span style="color:#66d9ef">[Convert]</span>::ToBase64String( <span style="color:#66d9ef">[Text.Encoding]</span>::ASCII.GetBytes( (<span style="color:#e6db74">&#34;{0}:{1}&#34;</span> <span style="color:#f92672">-f</span> <span style="color:#e6db74">&#39;&#39;</span>, $cred.GetNetworkCredential().Password) ) ) $body = @{ name = <span style="color:#e6db74">&#39;test-pwsh-env&#39;</span>; description = <span style="color:#e6db74">&#39;test environment from APR&#39;</span> } | ConvertTo-Json $headers = @{ Accept = <span style="color:#e6db74">&#39;application/json;&#39;</span>; Authorization = <span style="color:#e6db74">&#34;Basic {0}&#34;</span> <span style="color:#f92672">-f</span> $encodedValue } Invoke-RestMethod -Method POST -Uri $url -Body $body -Headers $headers -ContentType <span style="color:#e6db74">&#39;application/json&#39;</span> </code></pre></div><p>But when I execute the above code, it gives me an error.</p> <p><img src="https://dexterposh.github.io/static/002/pwsherror.png" alt="Error thrown"></p> <p>The error thrown is below.</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="color:#f92672">&#34;$id&#34;</span>: <span style="color:#e6db74">&#34;1&#34;</span>, <span style="color:#f92672">&#34;innerException&#34;</span>: <span style="color:#66d9ef">null</span>, <span style="color:#f92672">&#34;message&#34;</span>: <span style="color:#e6db74">&#34;No api-version was supplied for the \&#34;POST\&#34; request. The version must be supplied either as part of the Accept header (e.g. \&#34;application/json; api-version=1.0\&#34;) or as a query parameter (e.g. \&#34;?api-version=1.0\&#34;).&#34;</span>, <span style="color:#f92672">&#34;typeName&#34;</span>: <span style="color:#e6db74">&#34;Microsoft.VisualStudio.Services.WebApi.VssVersionNotSpecifiedException, Microsoft.VisualStudio.Services.WebApi&#34;</span>, <span style="color:#f92672">&#34;typeKey&#34;</span>: <span style="color:#e6db74">&#34;VssVersionNotSpecifiedException&#34;</span>, <span style="color:#f92672">&#34;errorCode&#34;</span>: <span style="color:#ae81ff">0</span>, <span style="color:#f92672">&#34;eventId&#34;</span>: <span style="color:#ae81ff">3000</span> } </code></pre></div><p>Read the error message, it explains that the api-version is missing. Also, looking back at the capture and see where the api-version was specified.</p> <p><img src="https://dexterposh.github.io/static/002/apiversion.png" alt="API Version in header"></p> <p>Let&rsquo;s make the same change in our code snippet to include API version in the header.</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-powershell" data-lang="powershell">$url = <span style="color:#e6db74">&#39;https://dev.azure.com/ddhami/BITPro.AzDeploy/_apis/distributedtask/environments&#39;</span> $cred = Get-Credential -UserName <span style="color:#e6db74">&#39;vsts&#39;</span> -Message <span style="color:#e6db74">&#39;Enter AzDO Personal Access Token with privs to create env&#39;</span> $encodedValue = <span style="color:#66d9ef">[Convert]</span>::ToBase64String( <span style="color:#66d9ef">[Text.Encoding]</span>::ASCII.GetBytes( (<span style="color:#e6db74">&#34;{0}:{1}&#34;</span> <span style="color:#f92672">-f</span> <span style="color:#e6db74">&#39;&#39;</span>, $cred.GetNetworkCredential().Password) ) ) $body = @{ name = <span style="color:#e6db74">&#39;test-pwsh-env&#39;</span>; description = <span style="color:#e6db74">&#39;test environment from APR&#39;</span> } | ConvertTo-Json $headers = @{ <span style="display:block;width:100%;background-color:#3c3d38"> Accept = <span style="color:#e6db74">&#39;application/json;api-version=5.0-preview.1&#39;</span>; </span> Authorization = <span style="color:#e6db74">&#34;Basic {0}&#34;</span> <span style="color:#f92672">-f</span> $encodedValue } Invoke-RestMethod -Method POST -Uri $url -Body $body -Headers $headers -ContentType = <span style="color:#e6db74">&#39;application/json&#39;</span> </code></pre></div><p>Here I go, this finally works and using the similar API endpoint I can fetch the environments as well (GET request).</p> <p><img src="https://dexterposh.github.io/static/002/envsuccess.png" alt="Env created success"></p> #1 Hello World https://dexterposh.github.io/posts/001-hello-world/ Tue, 14 Apr 2020 12:20:36 +0530 https://dexterposh.github.io/posts/001-hello-world/ <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">print(<span style="color:#e6db74">&#34;Hello World!&#34;</span>) </code></pre></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-powershell" data-lang="powershell">Write-Host -Object <span style="color:#e6db74">&#34;Hello World&#34;</span> </code></pre></div>