<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[RubberDuckDev]]></title><description><![CDATA[Blog posts by Dushyant Priyadarshee]]></description><link>https://rubberduckdev.com</link><generator>RSS for Node</generator><lastBuildDate>Mon, 08 Jun 2026 20:46:24 GMT</lastBuildDate><item><title><![CDATA[Microsoft Authenticator - "Unexpected error" when scanning QR code]]></title><description><![CDATA[Introduction While onboarding a brand new user to a tenant, the Microsoft Authenticator app refused to register the account, even though…]]></description><link>https://rubberduckdev.com/authenticator-qr-code-unexpected-error/</link><guid isPermaLink="false">https://rubberduckdev.com/authenticator-qr-code-unexpected-error/</guid><pubDate>Mon, 08 Jun 2026 10:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;While onboarding a brand new user to a tenant, the Microsoft Authenticator app refused to register the account, even though other users on the same tenant were registering without any issue. This post captures the symptoms and the fix so future-me (and you) don’t have to spend a week on a support ticket to find it.&lt;/p&gt;
&lt;h2&gt;The setup and issue&lt;/h2&gt;
&lt;p&gt;The flow that failed was a textbook first-time MFA setup:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;New user is created in Microsoft Entra ID.&lt;/li&gt;
&lt;li&gt;User signs in to &lt;code class=&quot;language-text&quot;&gt;https://login.microsoftonline.com&lt;/code&gt; with the temporary password.&lt;/li&gt;
&lt;li&gt;User sets a new password.&lt;/li&gt;
&lt;li&gt;User is prompted to set up MFA and chooses &lt;strong&gt;Work or school account&lt;/strong&gt; in Microsoft Authenticator.&lt;/li&gt;
&lt;li&gt;User scans the QR code.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At step 5, the Authenticator app fails with:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;plaintext&quot;&gt;&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Unable to add account.
Unexpected error. Please contact your local IT administrator to resolve the problem.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Things worth noting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Other (existing and newly created) users could add their accounts to Authenticator on different devices without any problem.&lt;/li&gt;
&lt;li&gt;The same Authenticator app on the affected device already had other work accounts registered and was working fine.&lt;/li&gt;
&lt;li&gt;The tenant configuration, Conditional Access, and user state all looked healthy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the failure was clearly local to the &lt;strong&gt;Authenticator app’s state on that one device&lt;/strong&gt;, not a tenant or account issue.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;The fix turned out to be a single, low-key option inside the Authenticator app:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authenticator app → Settings → Device Registration → Reset Device Notifications.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After triggering &lt;strong&gt;Reset Device Notifications&lt;/strong&gt;, the QR code scan completed and the account was added on the first try.&lt;/p&gt;
&lt;h2&gt;Why it works&lt;/h2&gt;
&lt;p&gt;When you add a work or school account, the Authenticator app must complete two things behind the scenes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Register the app/device with Microsoft Entra ID.&lt;/li&gt;
&lt;li&gt;Establish a push notification channel (via FCM on Android / APNs on iOS) so MFA prompts can be delivered.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the local notification registration state is stale or inconsistent - for example after OS updates, app updates, restoring from backup, or partially failed previous registrations - the registration handshake can fail and surface as the generic “Unexpected error”.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reset Device Notifications&lt;/strong&gt; clears that local state and forces the app to re-establish a fresh notification registration, which unblocks the new account add.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If you ever hit “Unable to add account. Unexpected error.” in Microsoft Authenticator and the tenant side looks healthy, try &lt;strong&gt;Reset Device Notifications&lt;/strong&gt; before going down the rabbit hole of factory resets or new devices. Hope this saves you some time.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[When git reset --hard Isn’t Enough]]></title><description><![CDATA[Introduction I ran into a small but frustrating issue recently. Git was telling me my branch was ahead of origin by a couple of commits…]]></description><link>https://rubberduckdev.com/git-reset-not-enough/</link><guid isPermaLink="false">https://rubberduckdev.com/git-reset-not-enough/</guid><pubDate>Fri, 05 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I ran into a small but frustrating issue recently. Git was telling me my branch was ahead of origin by a couple of commits, even though I just wanted to throw everything away and go back to the remote version.&lt;/p&gt;
&lt;p&gt;My first instinct was the usual:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--hard&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But nothing changed. The “2 unpushed commits” were still there.&lt;/p&gt;
&lt;h3&gt;What’s actually happening&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;git reset --hard&lt;/code&gt; on its own only resets your working directory to your current &lt;code class=&quot;language-text&quot;&gt;HEAD&lt;/code&gt;. If your local branch already includes those extra commits, Git considers that the correct state. So it doesn’t remove anything.&lt;/p&gt;
&lt;p&gt;In other words, you’re resetting to the wrong place.&lt;/p&gt;
&lt;h3&gt;The fix&lt;/h3&gt;
&lt;p&gt;What worked straight away was resetting to the remote branch instead:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; fetch origin
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--hard&lt;/span&gt; origin/branch-name&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Why this works&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;git fetch origin&lt;/code&gt; updates your view of the remote&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;git reset --hard origin/branch-name&lt;/code&gt; moves your local branch pointer back to exactly where the remote branch is&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This effectively discards any local commits that haven’t been pushed.&lt;/p&gt;
&lt;h3&gt;A quick note&lt;/h3&gt;
&lt;p&gt;This is a destructive operation. Any unpushed commits will be lost, so it’s worth double-checking that you really don’t need them.&lt;/p&gt;
&lt;h3&gt;Takeaway&lt;/h3&gt;
&lt;p&gt;If you’re trying to get your branch back in sync with the remote and &lt;code class=&quot;language-text&quot;&gt;git reset --hard&lt;/code&gt; doesn’t help, it’s probably because you’re resetting to your own &lt;code class=&quot;language-text&quot;&gt;HEAD&lt;/code&gt;. Point it at &lt;code class=&quot;language-text&quot;&gt;origin/branch-name&lt;/code&gt; instead, and the issue goes away.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why Try-Catch Doesn't Work with Pulumi]]></title><description><![CDATA[When working with Pulumi, especially coming from traditional .NET development, you might expect that wrapping resource creation in a try…]]></description><link>https://rubberduckdev.com/pulumi-try-catch/</link><guid isPermaLink="false">https://rubberduckdev.com/pulumi-try-catch/</guid><pubDate>Fri, 10 Oct 2025 16:30:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with Pulumi, especially coming from traditional .NET development, you might expect that wrapping resource creation in a try-catch block would handle deployment errors gracefully. However, as many developers discover, this approach doesn’t work as expected. In this post, we’ll explore why this happens and how to properly handle errors in Pulumi applications.&lt;/p&gt;
&lt;h2&gt;The Problem: When Try-Catch Fails&lt;/h2&gt;
&lt;p&gt;Consider this seemingly straightforward Pulumi C# program:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Storage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Deployment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RunAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Create an Azure resource (Storage Account)&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccountArgs&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;non-existent-rg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Intentionally incorrect&lt;/span&gt;
            Sku &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SkuArgs&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SkuName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Standard_LRS
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Kind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StorageV2
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; 
    &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// This should catch errors, right? Wrong!&lt;/span&gt;
        Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;An error occurred: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;ex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Running &lt;code class=&quot;language-text&quot;&gt;pulumi preview&lt;/code&gt; should work fine and it should report the expected values:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;+ pulumi:pulumi:Stack: (create)
    [urn=urn:pulumi:testtrycatch::pulumi-try-catch-fail::pulumi:pulumi:Stack::pulumi-try-catch-fail-testtrycatch]
    + azure-native:storage:StorageAccount: (create)
        [urn=urn:pulumi:testtrycatch::pulumi-try-catch-fail::azure-native:storage:StorageAccount::sa]
        [provider=urn:pulumi:testtrycatch::pulumi-try-catch-fail::pulumi:providers:azure-native::default_3_8_0::04da6b54-80e4-46f7-96ec-b56ff0331ba9]
        accountName         : &amp;quot;sa9203a871&amp;quot;
        azureApiVersion     : &amp;quot;2024-01-01&amp;quot;
        kind                : &amp;quot;StorageV2&amp;quot;
        location            : &amp;quot;westeurope&amp;quot;
        resourceGroupName   : &amp;quot;non-existent-rg&amp;quot;
        sku                 : {
            name: &amp;quot;Standard_LRS&amp;quot;
        }
Resources:
    + 2 to create&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When running &lt;code class=&quot;language-text&quot;&gt;pulumi up&lt;/code&gt; on this code, you might expect the try-catch to handle any deployment errors gracefully. Instead, you get an error like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;error: cannot check existence of resource &amp;#39;/subscriptions/.../resourceGroups/non-existent-rg/providers/Microsoft.Storage/storageAccounts/sa79a4d803&amp;#39;: status code 403, {&amp;quot;error&amp;quot;:{&amp;quot;code&amp;quot;:&amp;quot;AuthorizationFailed&amp;quot;,&amp;quot;message&amp;quot;:&amp;quot;The client &amp;#39;...&amp;#39; does not have authorization to perform action &amp;#39;Microsoft.Storage/storageAccounts/read&amp;#39; over scope &amp;#39;...&amp;#39; or the scope is invalid.&amp;quot;}}
error: update failed&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Three questions arise:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Why doesn’t the try-catch hide the error?&lt;/li&gt;
&lt;li&gt;Why doesn’t the error mention that the resource group doesn’t exist?&lt;/li&gt;
&lt;li&gt;Why isn’t the Console.WriteLine message displayed?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Understanding Pulumi’s Execution Model&lt;/h2&gt;
&lt;h3&gt;The Asynchronous, Declarative Nature&lt;/h3&gt;
&lt;p&gt;The fundamental issue lies in understanding &lt;strong&gt;when&lt;/strong&gt; Pulumi actually creates resources. When you write:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccountArgs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token range operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This line does &lt;strong&gt;not&lt;/strong&gt; immediately create the Azure resource. Instead, it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Creates a Pulumi resource object&lt;/strong&gt; in memory&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Registers it&lt;/strong&gt; with the Pulumi engine for later deployment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Returns immediately&lt;/strong&gt; without making any Azure API calls&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The actual Azure API calls happen later during the &lt;strong&gt;deployment phase&lt;/strong&gt;, which occurs after your C# code has completely finished executing.&lt;/p&gt;
&lt;h3&gt;Why Try-Catch Doesn’t Work&lt;/h3&gt;
&lt;p&gt;The try-catch block in your code only catches exceptions that occur during the &lt;strong&gt;registration phase&lt;/strong&gt; (steps 1-2 above), not during the &lt;strong&gt;deployment phase&lt;/strong&gt; where the actual Azure API calls are made.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// This only registers the resource - no Azure API calls yet&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Execution reaches here successfully&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; 
&lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// This will never execute for deployment errors&lt;/span&gt;
    Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;An error occurred: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;ex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// C# code completes here&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// THEN Pulumi makes the actual Azure API calls&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Why the Error Message Doesn’t Mention Resource Group&lt;/h2&gt;
&lt;p&gt;The error you’re seeing is an &lt;strong&gt;authorization error (HTTP 403)&lt;/strong&gt;, not a “resource not found” error (HTTP 404). Here’s the sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Azure checks permissions first&lt;/strong&gt;: Before checking if resources exist, Azure validates whether you have the required permissions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permission denied&lt;/strong&gt;: Since the resource doesn’t exist, it assumes user doesn’t have &lt;code class=&quot;language-text&quot;&gt;Microsoft.Storage/storageAccounts/read&lt;/code&gt; permission on that scope, Azure returns a 403 error&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Early termination&lt;/strong&gt;: Azure’s RBAC (Role-Based Access Control) evaluation happens before resource existence validation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So we get a 403 as permissions could not be validated.&lt;/p&gt;
&lt;h2&gt;Proper Error Handling Strategies in Pulumi&lt;/h2&gt;
&lt;h3&gt;1. Validate Dependencies First&lt;/h3&gt;
&lt;p&gt;The most straightforward approach is to ensure your dependencies exist:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Deployment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RunAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Create the resource group first&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; resourceGroup &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ResourceGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;rg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ResourceGroupArgs&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        Location &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WestEurope&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Use the created resource group&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccountArgs&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resourceGroup&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Reference the created RG&lt;/span&gt;
        Sku &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SkuArgs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SkuName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Standard_LRS &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        Kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Kind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StorageV2
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2. Use Pulumi’s Apply Methods for Output Handling&lt;/h3&gt;
&lt;p&gt;You can handle errors in resource outputs using the &lt;code class=&quot;language-text&quot;&gt;Apply&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccountArgs&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;my-resource-group&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    Sku &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SkuArgs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SkuName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Standard_LRS &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    Kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Kind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StorageV2
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Handle the output with potential error scenarios&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccountId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; storageAccount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; 
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;Storage account created with ID: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;3. Use Stack Transformations for Advanced Scenarios&lt;/h3&gt;
&lt;p&gt;For more complex error handling or resource modification scenarios:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Deployment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RunAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Set up stack transformation for resource validation&lt;/span&gt;
    Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Deployment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Instance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RegisterStackTransformation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// You can validate or modify resources here before deployment&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Resource &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StorageAccount&lt;/span&gt; sa&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Add validation logic&lt;/span&gt;
            Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;Validating storage account: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccountArgs&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;my-resource-group&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        Sku &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SkuArgs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SkuName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Standard_LRS &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        Kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Kind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StorageV2
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;4. Use Pulumi’s Built-in Resource Options&lt;/h3&gt;
&lt;p&gt;Pulumi provides several resource options for handling various scenarios:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccountArgs&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;my-resource-group&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    Sku &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SkuArgs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SkuName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Standard_LRS &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    Kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Kind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StorageV2
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;CustomResourceOptions&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Ignore changes to specific properties&lt;/span&gt;
    IgnoreChanges &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tags&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// Protect the resource from deletion&lt;/span&gt;
    Protect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// Set explicit dependencies&lt;/span&gt;
    DependsOn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* other resources */&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Best Practices for Error Prevention&lt;/h2&gt;
&lt;h3&gt;Use Resource References Instead of Hard-coded Names&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Bad: Hard-coded resource group name&lt;/span&gt;
ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;my-resource-group&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Good: Reference to created resource&lt;/span&gt;
ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resourceGroup&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Understanding Pulumi’s asynchronous, declarative execution model is crucial for effective error handling. Traditional try-catch blocks work for registration-time errors but not for deployment-time errors.&lt;/p&gt;
&lt;h2&gt;Additional Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pulumi.com/docs/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Pulumi Documentation on Error Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/role-based-access-control/troubleshooting&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure RBAC Troubleshooting Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pulumi.com/docs/guides/best-practices/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Pulumi Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;For more insights on Infrastructure as Code and cloud development best practices, follow my blog or connect with me on LinkedIn.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Git Branch Naming Conflicts in Azure DevOps]]></title><description><![CDATA[Special thanks to my fantastic colleague Fatih Kucukkara for helping figure these details out. 🧩 What They Are and How to Fix Them If you…]]></description><link>https://rubberduckdev.com/git-ado-branch-case-sensitivity/</link><guid isPermaLink="false">https://rubberduckdev.com/git-ado-branch-case-sensitivity/</guid><pubDate>Wed, 01 Oct 2025 17:30:00 GMT</pubDate><content:encoded>&lt;p&gt;Special thanks to my fantastic colleague &lt;a href=&quot;https://www.linkedin.com/in/fatih-kucukkara-msc-0b480057/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Fatih Kucukkara&lt;/a&gt; for helping figure these details out.&lt;/p&gt;
&lt;h2&gt;🧩 What They Are and How to Fix Them&lt;/h2&gt;
&lt;p&gt;If you’ve ever pushed a branch to Azure DevOps and been greeted with a cryptic error like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;[remote rejected] bug/456789-fix-something -&amp;gt; bug/456789-fix-something (Name conflicts with refs/heads/Bug/987654-fix-another-thing)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You’re not alone. This error can be frustrating, especially when everything looks fine locally. Let’s break down what’s happening and how to resolve it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;🔍 Understanding the Error&lt;/h2&gt;
&lt;p&gt;This error means that &lt;strong&gt;Azure DevOps is rejecting your push because the branch name you’re trying to use conflicts with an existing branch name&lt;/strong&gt;—but not in the way you might expect.&lt;/p&gt;
&lt;h3&gt;The culprit? &lt;strong&gt;Case sensitivity.&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;While Git is case-sensitive on most systems, &lt;strong&gt;Azure DevOps treats branch names as case-insensitive&lt;/strong&gt;. So if you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;bug/456789-fix-something&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Bug/987654-fix-another-thing&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Azure DevOps sees these as potentially conflicting because it doesn’t distinguish between &lt;code class=&quot;language-text&quot;&gt;bug/&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Bug/&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;🛠️ How to Fix It&lt;/h2&gt;
&lt;p&gt;Here are a few ways to resolve this issue:&lt;/p&gt;
&lt;h3&gt;✅ 1. Rename Your Branch Locally&lt;/h3&gt;
&lt;p&gt;Avoid the conflict by renaming your branch to something unique:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;git branch -m bug/456789-fix-something bugfix/456789-fix-something
git push origin bugfix/456789-fix-something&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This sidesteps the naming conflict entirely.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;✅ 2. Delete the Conflicting Remote Branch (If Safe)&lt;/h3&gt;
&lt;p&gt;If the conflicting branch is no longer needed and you’re sure it’s safe to remove:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;git push origin --delete Bug/987654-fix-another-thing&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then retry your push.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;✅ 3. Adopt a Clear Naming Convention&lt;/h3&gt;
&lt;p&gt;To prevent future conflicts, consider using a consistent and distinct naming convention for branches—such as &lt;code class=&quot;language-text&quot;&gt;feature/&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;bugfix/&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;hotfix/&lt;/code&gt;, etc.—and avoid mixing cases.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;🧠 Final Thoughts&lt;/h2&gt;
&lt;p&gt;This issue is a great reminder that &lt;strong&gt;case sensitivity can behave differently across systems&lt;/strong&gt;, and that naming conventions matter—especially in collaborative environments like Azure DevOps.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Multi-Agent AI App with Microsoft Semantic Kernel]]></title><description><![CDATA[Building Multi-Agent AI Applications with Microsoft Semantic Kernel The rise of AI-powered content generation has opened new possibilities…]]></description><link>https://rubberduckdev.com/multi-agent-app-semantic-kernel/</link><guid isPermaLink="false">https://rubberduckdev.com/multi-agent-app-semantic-kernel/</guid><pubDate>Sat, 27 Sep 2025 21:05:52 GMT</pubDate><content:encoded>&lt;div className=&quot;seo-hidden&quot;&gt;
Learn how to build sophisticated AI applications using Microsoft Semantic Kernel with multi-agent architecture. Covers Azure OpenAI, Google AI Studio, Docker deployment, and innovative use cases for specialized AI agents.
&lt;/div&gt;
&lt;h1&gt;Building Multi-Agent AI Applications with Microsoft Semantic Kernel&lt;/h1&gt;
&lt;p&gt;The rise of AI-powered content generation has opened new possibilities for automating complex workflows. By combining Microsoft Semantic Kernel with a multi-agent architecture, developers can create sophisticated applications that break down complex tasks into specialized, manageable components.&lt;/p&gt;
&lt;h2&gt;What is Microsoft Semantic Kernel?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/semantic-kernel/overview/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft Semantic Kernel&lt;/a&gt; is an open-source SDK that enables developers to integrate AI services into their applications seamlessly. It provides a unified interface for working with various AI models, whether from Azure OpenAI, Google AI, or local deployments. The framework emphasizes function-based AI interactions, allowing developers to define specific capabilities as kernel functions. It supports &lt;a href=&quot;https://learn.microsoft.com/en-us/semantic-kernel/get-started/supported-languages?pivots=programming-language-csharp&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;multiple languages&lt;/a&gt;, in this post we will use C#.&lt;/p&gt;
&lt;p&gt;The dotnet package we need to use Semantic Kernel in dotnet:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;dotnet package add Microsoft.SemanticKernel&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A sample Kernel Function example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;KernelFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Write blog post content based on research outline&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;WriteContentAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; outline&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; tone &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Professional&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; prompt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;You are an expert content writer...&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; _kernel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;InvokePromptAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prompt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Multi-Agent Architecture: Divide and Conquer&lt;/h2&gt;
&lt;p&gt;Traditional AI applications often try to handle everything in a single prompt or interaction. Multi-agent systems take a different approach by creating specialized agents, each responsible for a specific aspect of the workflow. This architecture offers several advantages:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Specialized Expertise&lt;/strong&gt;: Each agent focuses on one task—research, writing, editing, SEO optimization, or markdown formatting. This specialization leads to higher quality outputs as each agent can be fine-tuned for its specific role.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;: Changes to one agent don’t affect others, making the system easier to maintain and extend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pipeline Processing&lt;/strong&gt;: Complex tasks are broken into manageable steps, with each agent building upon the previous one’s output.&lt;/p&gt;
&lt;p&gt;A typical multi-agent blog generation pipeline might flow like this:
Research Agent → Content Writer → Editor → Markdown Linter → SEO Optimizer&lt;/p&gt;
&lt;h2&gt;Flexible AI Provider Support&lt;/h2&gt;
&lt;p&gt;Modern applications need flexibility in choosing AI providers based on cost, performance, and availability requirements.&lt;/p&gt;
&lt;h3&gt;Azure OpenAI Integration&lt;/h3&gt;
&lt;p&gt;Azure OpenAI provides enterprise-grade AI services with enhanced security and compliance features. Integration is straightforward:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;dotnet package add Microsoft.SemanticKernel.Connectors.OpenAI&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AddAzureOpenAIChatCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token named-parameter punctuation&quot;&gt;deploymentName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gpt-4o-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token named-parameter punctuation&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://your-deployment.openai.azure.com/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token named-parameter punctuation&quot;&gt;apiKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;your-api-key&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Important - do not use in plain text, use a secured mechanism&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Google AI Studio: Free Tier Benefits&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://aistudio.google.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Google AI Studio&lt;/a&gt; offers competitive pricing with &lt;a href=&quot;https://ai.google.dev/gemini-api/docs/pricing&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;a generous free tier&lt;/a&gt;, making it an excellent choice for development and small-scale applications. According to Google’s pricing documentation, developers can access Gemini models with substantial free monthly quotas before incurring charges. This makes it particularly attractive for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prototyping and development&lt;/li&gt;
&lt;li&gt;Educational projects&lt;/li&gt;
&lt;li&gt;Small-scale applications&lt;/li&gt;
&lt;li&gt;Cost-conscious implementations&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;dotnet package add Microsoft.SemanticKernel.Connectors.Google --prerelease&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AddGoogleAIGeminiChatCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token named-parameter punctuation&quot;&gt;modelId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &quot;gemini&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2.5&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;pro&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token named-parameter punctuation&quot;&gt;apiKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;your-api-key&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Important - do not use in plain text, use a secured mechanism&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Local Development with Docker&lt;/h3&gt;
&lt;p&gt;For development environments or when working with sensitive data, running models locally provides complete control and privacy. &lt;a href=&quot;https://docs.docker.com/ai/model-runner/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Docker Model Runner (DMR)&lt;/a&gt; makes this surprisingly accessible:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;# Pull and run a local AI model
docker model pull ai/gemma3
docker model run ai/gemma3&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Create HttpClient with longer timeout as local run can take more than 100ms&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; httpClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;HttpClient&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Increase timeout as local runs take long&lt;/span&gt;
    Timeout &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TimeSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FromMinutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AddOpenAIChatCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token named-parameter punctuation&quot;&gt;modelId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ai/gemma3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// note that the id has to match exactly what we pulled&lt;/span&gt;
                apiKey&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;not-required&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token named-parameter punctuation&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Uri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:12434/engines/v1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// the port number is set in docker-desktop -&gt; Settings -&gt; AI -&gt; Enable Docker Model Runner -&gt; Enable host-side TCP support -&gt; Port&lt;/span&gt;
                httpClient&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; httpClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Local deployment offers several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Privacy&lt;/strong&gt;: Data never leaves your environment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost Control&lt;/strong&gt;: No per-token charges&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offline Capability&lt;/strong&gt;: Works without internet connectivity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customization&lt;/strong&gt;: Full control over model parameters&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Implementation Benefits&lt;/h2&gt;
&lt;p&gt;This multi-agent approach delivers tangible benefits:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quality Through Specialization&lt;/strong&gt;: Each agent excels at its specific task rather than being a generalist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Individual agents can be optimized, replaced, or scaled independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging&lt;/strong&gt;: Issues can be traced to specific agents, making troubleshooting more straightforward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;: Easy to swap AI providers or add new agents without restructuring the entire application.&lt;/p&gt;
&lt;h2&gt;Future Enhancement Ideas&lt;/h2&gt;
&lt;p&gt;The multi-agent architecture opens doors for exciting improvements:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Intelligent Routing&lt;/strong&gt;: Implement dynamic agent selection based on content type, complexity, or quality requirements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quality Scoring&lt;/strong&gt;: Add agents that evaluate output quality and trigger rewrites when necessary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-Modal Capabilities&lt;/strong&gt;: Integrate image generation agents for creating accompanying visuals, diagrams, or social media graphics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real-Time Fact Checking&lt;/strong&gt;: Incorporate agents that verify claims against current data sources or knowledge bases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Personalization Engine&lt;/strong&gt;: Develop agents that adapt content style and complexity based on reader preferences or analytics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Translation Pipeline&lt;/strong&gt;: Add multilingual agents for automatic content localization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance Analytics&lt;/strong&gt;: Implement agents that analyze content performance and suggest optimizations.&lt;/p&gt;
&lt;h2&gt;Beyond Content: Creative Multi-Agent Applications&lt;/h2&gt;
&lt;p&gt;The multi-agent paradigm extends far beyond content generation into innovative domains:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Digital Archaeology&lt;/strong&gt;: Deploy specialized agents to reconstruct lost digital assets—one agent analyzes fragmented code repositories, another reconstructs missing documentation, while a third validates historical API behaviors through pattern recognition.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Synthetic Dataset Generation&lt;/strong&gt;: Create agents that collaborate to generate realistic training data. A demographic agent creates diverse user profiles, a behavioral agent simulates realistic interaction patterns, and a validation agent ensures statistical authenticity without privacy violations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adaptive Learning Orchestration&lt;/strong&gt;: Build educational systems where agents specialize in different learning modalities—one tracks cognitive load through interaction patterns, another adjusts difficulty curves in real-time, and a third generates personalized analogies based on the learner’s background knowledge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Regulatory Compliance Automation&lt;/strong&gt;: Deploy agents across different regulatory frameworks where each agent becomes an expert in specific compliance domains (GDPR, SOX, HIPAA), automatically scanning systems and suggesting remediation strategies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Creative Ideation Networks&lt;/strong&gt;: Implement agents that approach problem-solving from different creative methodologies—one uses lateral thinking techniques, another applies biomimicry principles, while a third challenges assumptions through contrarian analysis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Automated Research Hypothesis Generation&lt;/strong&gt;: Deploy agents that scan scientific literature, identify knowledge gaps, and propose novel research directions by combining insights across traditionally separate domains.&lt;/p&gt;
&lt;p&gt;These applications demonstrate how multi-agent systems can tackle complex, nuanced problems that require diverse expertise and collaborative intelligence—opening possibilities we’re only beginning to explore.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Multi-agent AI systems represent a maturation of AI application development, moving beyond monolithic AI interactions toward specialized, maintainable, and scalable architectures. By leveraging tools like Microsoft Semantic Kernel and embracing the multi-agent paradigm, developers can create applications that are not only more capable but also more reliable and easier to maintain.&lt;/p&gt;
&lt;p&gt;Whether you’re building content generation tools, data analysis pipelines, or complex decision support systems, the multi-agent approach offers a pathway to more sophisticated and maintainable AI applications.&lt;/p&gt;
&lt;p&gt;Please note, I have &lt;a href=&quot;https://github.com/realrubberduckdev/blog-gen/tree/old-semantic-kernel-implementation&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;this old branch using Semantic Kernel&lt;/a&gt; for reference if anyone is interested. The repo has now moved to use &lt;a href=&quot;https://github.com/microsoft/agent-framework&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft Agent Framework&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Update Docker Desktop CLI - Fast & Easy]]></title><description><![CDATA[Level Up Your Docker Workflow Are you tired of the tedious process of updating Docker Desktop? Or maybe your corporate policies is blocking…]]></description><link>https://rubberduckdev.com/update-docker-desktop/</link><guid isPermaLink="false">https://rubberduckdev.com/update-docker-desktop/</guid><pubDate>Sat, 27 Sep 2025 11:05:52 GMT</pubDate><content:encoded>&lt;div className=&quot;seo-hidden&quot;&gt;
This guide demonstrates how to efficiently update Docker Desktop directly from the command line using the Docker Desktop CLI. It&apos;s a faster, more reliable alternative to the GUI, ideal for developers and DevOps teams. Learn step-by-step instructions and scripting tips for automated updates.
&lt;/div&gt;
&lt;h1&gt;Level Up Your Docker Workflow&lt;/h1&gt;
&lt;p&gt;Are you tired of the tedious process of updating Docker Desktop? Or maybe your corporate policies is blocking you from updating docker from the UI? Fortunately, there’s a smarter, faster way. The Docker Desktop CLI offers a powerful and reliable solution for updating Docker Desktop directly from the command line, significantly boosting your productivity and streamlining your workflows.&lt;/p&gt;
&lt;h2&gt;What is the Docker Desktop CLI?&lt;/h2&gt;
&lt;p&gt;The Docker Desktop CLI (Command-Line Interface) is a tool that provides a way to manage key features of Docker Desktop directly from your terminal. Introduced in version 4.39, it’s surprisingly easy to use, refer to the official documentation: &lt;a href=&quot;https://docs.docker.com/desktop/features/desktop-cli/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;https://docs.docker.com/desktop/features/desktop-cli/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Step-by-Step Update Process&lt;/h2&gt;
&lt;p&gt;Let’s walk through the simple process of updating Docker Desktop using the CLI:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Verify Version&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First, check your current Docker Desktop version:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;docker version&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This command will display information about your Docker client and server versions. Ensure your Docker Desktop version is 4.39 or later to utilize the CLI’s update functionality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Execute the Update Command&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now, initiate the update process:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;docker desktop update&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This command tells Docker Desktop to check for available updates and apply them. Behind the scenes, the CLI meticulously checks for new versions, downloads the necessary files, and restarts Docker Desktop if required – all without the need to manually interact with the GUI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Post-Update Verification&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After the update completes, confirm the new version:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pwsh&quot;&gt;&lt;pre class=&quot;language-pwsh&quot;&gt;&lt;code class=&quot;language-pwsh&quot;&gt;docker version&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You should now see the updated Docker version displayed.&lt;/p&gt;
&lt;h2&gt;Linux Update Considerations&lt;/h2&gt;
&lt;p&gt;It’s important to note that Docker Desktop isn’t the primary tool for managing updates on Linux systems. Instead, utilize your distribution’s package manager (apt, yum, dnf) to ensure a consistent and supported Docker installation. For example, on Debian/Ubuntu:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; update &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; upgrade&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Conclusion &amp;#x26; Call to Action&lt;/h2&gt;
&lt;p&gt;The Docker Desktop CLI is a valuable tool that can significantly improve your Docker workflow efficiency. By automating updates and providing greater control, it’s a must-have for developers, DevOps engineers, and students alike. Start leveraging the CLI today and experience the difference! Explore the official documentation &lt;a href=&quot;https://docs.docker.com/desktop/features/desktop-cli/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;https://docs.docker.com/desktop/features/desktop-cli/&lt;/a&gt; to learn more and unlock the full potential of your Docker Desktop environment.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Offline AI - Integrating Local LLMs with VSCode]]></title><description><![CDATA[Introduction In the age of AI, developers are increasingly looking for ways to integrate powerful language models into their workflows…]]></description><link>https://rubberduckdev.com/continue-local-llm-integration/</link><guid isPermaLink="false">https://rubberduckdev.com/continue-local-llm-integration/</guid><pubDate>Thu, 15 May 2025 14:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In the age of AI, developers are increasingly looking for ways to integrate powerful language models into their workflows. While cloud-based solutions like OpenAI’s GPT models are popular, there’s a growing demand for &lt;strong&gt;offline and private alternatives&lt;/strong&gt;. This guide walks you through integrating a &lt;strong&gt;locally running LLM&lt;/strong&gt; (DeepSeek R1 Distill LLaMA) with the &lt;a href=&quot;https://continue.dev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Continue&lt;/a&gt; extension in Visual Studio Code.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Why Offline Mode is Useful&lt;/h2&gt;
&lt;p&gt;Running AI models locally offers several advantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Privacy&lt;/strong&gt;: Sensitive codebases and data never leave your machine, ensuring compliance with privacy policies and regulations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed&lt;/strong&gt;: Local models eliminate latency caused by network requests, providing near-instant responses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost Efficiency&lt;/strong&gt;: Avoid recurring API costs by leveraging your local hardware.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customization&lt;/strong&gt;: Fine-tune models to your specific needs without relying on external providers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offline Access&lt;/strong&gt;: Work uninterrupted, even without an internet connection.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For developers who value control and independence, offline AI development is a game-changer.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The full details is on github at &lt;a href=&quot;https://github.com/realrubberduckdev/continue-local-llm-integration&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;continue-local-llm-integration&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;🐳 Step 1: Run the LLM in Docker&lt;/h2&gt;
&lt;p&gt;To get started, you’ll need to run the DeepSeek R1 Distill LLaMA model locally using Docker. This model is OpenAI-compatible and provides a robust foundation for offline AI development.&lt;/p&gt;
&lt;h3&gt;Resources:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.docker.com/blog/run-llms-locally/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Run LLMs Locally with Docker: A Quickstart Guide to Model Runner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/rGGZJT3ZCvo?si=ihU14pUpyw3gL0Zk&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Turn Your Mac Into an AI Playground with Docker Model Runner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/ai/deepseek-r1-distill-llama&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;deepseek-r1-distill-llama&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Steps:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Pull the DeepSeek model from Docker Hub.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start the container and ensure it exposes an endpoint like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:12434/engines/v1/chat/completions&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Test the setup using the provided &lt;code class=&quot;language-text&quot;&gt;test.ps1&lt;/code&gt; script in this repository.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;🧠 Step 2: Install the Continue Extension&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=Continue.continue&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Continue&lt;/a&gt; extension for Visual Studio Code is a powerful tool that brings AI-assisted coding to your IDE. It supports OpenAI-compatible APIs, making it an ideal choice for integrating local LLMs.&lt;/p&gt;
&lt;h3&gt;Installation:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Open Visual Studio Code.&lt;/li&gt;
&lt;li&gt;Navigate to the Extensions Marketplace.&lt;/li&gt;
&lt;li&gt;Search for “Continue” and install the extension.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;⚙️ Step 3: Configure Continue&lt;/h2&gt;
&lt;p&gt;To connect the Continue extension to your locally running LLM, you’ll need to update its configuration.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Locate the &lt;code class=&quot;language-text&quot;&gt;sample-config\config.yaml&lt;/code&gt; file in this repository.&lt;/li&gt;
&lt;li&gt;Copy the file to &lt;code class=&quot;language-text&quot;&gt;%userprofile%/.continue&lt;/code&gt; on your machine.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update the configuration to point to your local LLM endpoint:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;api_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;12434/engines/v1/chat/completions&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;⚽ Step 4: Start Coding with AI&lt;/h2&gt;
&lt;p&gt;Once everything is set up, you can start leveraging the power of local AI models directly in your IDE. The Continue extension provides features like code completion, refactoring suggestions, and more.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;By running models offline, you gain privacy, speed, and control over your development environment. Having said so, running models locally takes up a lot of resource, at least on my machine. Hopefully the experience is smoother on yours.&lt;/p&gt;
&lt;h2&gt;Credits&lt;/h2&gt;
&lt;p&gt;Banner image from &lt;a href=&quot;https://unsplash.com/photos/a-close-up-of-a-computer-board-with-a-logo-on-it-jk_nkEXo4aY?utm_content=creditShareLink&amp;#x26;utm_medium=referral&amp;#x26;utm_source=unsplash&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;BoliviaInteligente&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Avoid Storing Secrets in PowerShell's Command History]]></title><description><![CDATA[Introduction If you’re using PowerShell scripts to manage secrets like API tokens, passwords, or other sensitive data, you may think you’re…]]></description><link>https://rubberduckdev.com/ps-secret-in-history/</link><guid isPermaLink="false">https://rubberduckdev.com/ps-secret-in-history/</guid><pubDate>Mon, 17 Mar 2025 17:30:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;If you’re using PowerShell scripts to manage secrets like API tokens, passwords, or other sensitive data, you may think you’re safe by using environment variables or script parameters like &lt;code class=&quot;language-text&quot;&gt;$PAT&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;$SomePassword&lt;/code&gt; to avoid hardcoding secrets in your scripts. However, there’s a common pitfall that many PowerShell users overlook: the PowerShell command history.&lt;/p&gt;
&lt;p&gt;PowerShell saves every command you execute in a file located at:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cmd&quot;&gt;&lt;pre class=&quot;language-cmd&quot;&gt;&lt;code class=&quot;language-cmd&quot;&gt;%userprofile%\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This file stores all your PowerShell commands, including those that might contain sensitive information, like manually setting a parameter for a script:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$PAT&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&apos;mySuperSecretToken&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Even though you’re not committing this secret to Git, it’s still saved in plain text in your command history, which means it’s just as vulnerable if someone accesses your machine. In this blog post, we’ll look at how to prevent this from happening.&lt;/p&gt;
&lt;h2&gt;Why is this a Security Concern?&lt;/h2&gt;
&lt;p&gt;If anyone gains access to your machine and navigates to the &lt;code class=&quot;language-text&quot;&gt;ConsoleHost_history.txt&lt;/code&gt; file, they can see all the commands you’ve executed, including any credentials or secrets you’ve input manually.&lt;/p&gt;
&lt;p&gt;Here’s an example of what could happen:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;plaintext&quot;&gt;&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;cd C:\Projects\MyProject
$SomePassword = &apos;superSecurePassword123!&apos;
git push origin main&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A simple look at your PowerShell history could reveal sensitive data like passwords, API tokens, or private keys.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;How to Prevent Secrets from Appearing in PowerShell History&lt;/h2&gt;
&lt;h3&gt;1. Clear History After Entering Sensitive Information&lt;/h3&gt;
&lt;p&gt;One simple way to prevent secrets from being stored is to clear the PowerShell history immediately after running a sensitive command:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Clear-History&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will remove all commands from the current session’s memory. However, it doesn’t retroactively remove sensitive commands already written to &lt;code class=&quot;language-text&quot;&gt;ConsoleHost_history.txt&lt;/code&gt;. You should also regularly clear the file manually:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Remove-Item&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$env&lt;/span&gt;:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;2. Use Secure String for Secrets&lt;/h3&gt;
&lt;p&gt;Rather than passing secrets as plain text, you can use PowerShell’s &lt;code class=&quot;language-text&quot;&gt;SecureString&lt;/code&gt; to store sensitive data. While this won’t stop it from being written to the history, it helps protect it when stored or transmitted:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$SecurePassword&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Read-Host&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Enter Password&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;AsSecureString&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This method prompts the user to enter the secret interactively, and the input won’t be logged in the command history.&lt;/p&gt;
&lt;h3&gt;3. Disable Command History&lt;/h3&gt;
&lt;p&gt;If you often deal with sensitive information in your PowerShell sessions, it might be best to disable the history feature entirely. You can do this by editing the PowerShell profile file:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open the PowerShell profile for editing:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;notepad &lt;span class=&quot;token variable&quot;&gt;$PROFILE&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following line to disable PSReadLine’s history recording:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Set-PSReadlineOption&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;HistorySaveStyle SaveNothing&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Save the file and restart PowerShell. Now, PowerShell won’t save any commands to history.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4. Use Environment Variables for Sensitive Data&lt;/h3&gt;
&lt;p&gt;To further reduce risk, you can store sensitive information in environment variables. These won’t be saved to history, as they are set outside the scope of PowerShell:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;powershell&quot;&gt;&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$PAT&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$env&lt;/span&gt;:MY_PERSONAL_ACCESS_TOKEN&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Just make sure that environment variables containing secrets are not stored in plaintext in scripts or configuration files.&lt;/p&gt;
&lt;h2&gt;Best Practices for Handling Secrets&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Avoid typing secrets directly into the PowerShell prompt&lt;/strong&gt;. Always use &lt;code class=&quot;language-text&quot;&gt;Read-Host&lt;/code&gt; with the &lt;code class=&quot;language-text&quot;&gt;-AsSecureString&lt;/code&gt; flag to securely capture input.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clear the command history&lt;/strong&gt; if you’ve entered sensitive information manually during a session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Store secrets in a dedicated secret management system&lt;/strong&gt; (like Azure Key Vault, HashiCorp Vault, or AWS Secrets Manager), and retrieve them programmatically instead of hardcoding or passing them manually.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disable history recording&lt;/strong&gt; when working in environments where sensitive data will be routinely entered.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regularly audit your command history&lt;/strong&gt; to ensure no sensitive data is accidentally exposed.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The PowerShell command history can be a double-edged sword: it’s helpful for recalling past commands, but it can expose sensitive information if not managed carefully. If you’re working with secrets in PowerShell, take steps to prevent them from ending up in the command history file by using secure input methods, clearing history, or disabling it altogether. By following the best practices outlined above, you can better safeguard sensitive data in your PowerShell environment.&lt;/p&gt;
&lt;p&gt;Stay safe and keep your secrets secure!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Comparing Two Branches in Azure DevOps]]></title><description><![CDATA[This is a nice trick, which I was not aware of, even though I have been using Azure DevOps for many years. So thought will write a post…]]></description><link>https://rubberduckdev.com/azure-devops-compare-branches/</link><guid isPermaLink="false">https://rubberduckdev.com/azure-devops-compare-branches/</guid><pubDate>Sat, 25 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a nice trick, which I was not aware of, even though I have been using Azure DevOps for many years. So thought will write a post about it so hopefully someone else benefits from this.&lt;/p&gt;
&lt;p&gt;When working with multiple branches in a repository, it’s useful to compare them to understand the differences and ensure that changes are correctly merged. Azure DevOps provides a straightforward way to compare branches and review the changes.&lt;/p&gt;
&lt;h3&gt;Step-by-Step Guide&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Navigate to Your Repository&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open your Azure DevOps project.&lt;/li&gt;
&lt;li&gt;Go to the “Repos” section from the left-hand menu.&lt;/li&gt;
&lt;li&gt;Select the repository you want to work with.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Select the Branches&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 971px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b7e876443a81fc4d0f9d981fb4254efe/589d8/compare-branches-option.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.69624573378839%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABqElEQVQ4y51TSY7bMBDkPxIEtmxL1EKJm3bKywSjAYyZDPKJXHLKo/LOCroHMpwA2XQoUBTJYnV1UQx9h1RK7HcRDvvdPyM+7BHHMeLNO3x4+YbN1+94P3+BKJVCkadM+Cfs7r+jLWQSw1kDazSs86isR1FWEM5aqCJHqQoeF9zmqoCuFJzRUEXxRmA02qbmUVclqrKAyjOG6NoG3lk4ZzGFEePQg2wwuuID3mo0Q4AJj+hqi6apWRld4KyGMRqN12hcyReK4xRwOZ8QxoGJFkK6aME8z3h5/Yww9qD9tB5CYJWM2uLp8QHX6xUiz1Iur8gzpDK5IUvlDZWiUivQXgL9K8vyjXgcuJLTccIUAgQZHG03bPTvQOvbzeanOTXlvjpdKsjkAPE/UVlAnU5TiTAFhDDCecc9IPXib3H5FURIKguZ4DL0jM5o1EazFasURtEWfZ7h9XTEp/MZz9OEj96vJySFKpW4jAMepoCZfOxatmG1h1ImGIYebdugrj1neb3CXYRMSn4IhLb2HB1uylqFSXxA7R36rmV19EwpSmIN2TLeZ3hJwQ95ScJ2KdLoxQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;compare branches option&quot;
        title=&quot;compare branches option&quot;
        src=&quot;/static/b7e876443a81fc4d0f9d981fb4254efe/589d8/compare-branches-option.png&quot;
        srcset=&quot;/static/b7e876443a81fc4d0f9d981fb4254efe/8890b/compare-branches-option.png 293w,
/static/b7e876443a81fc4d0f9d981fb4254efe/1f316/compare-branches-option.png 585w,
/static/b7e876443a81fc4d0f9d981fb4254efe/589d8/compare-branches-option.png 971w&quot;
        sizes=&quot;(max-width: 971px) 100vw, 971px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click on the “Branches” tab to view all the branches in your repository.&lt;/li&gt;
&lt;li&gt;Identify the two branches you want to compare.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initiate the Comparison&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click on the branch you want to compare from.&lt;/li&gt;
&lt;li&gt;In the branch details view, click on the “Compare” button.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Choose the Target Branch&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the comparison view, select the target branch you want to compare against from the dropdown menu.&lt;/li&gt;
&lt;li&gt;Azure DevOps will display the differences between the two branches, including commits, file changes, and code differences.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Review the Changes&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the comparison view to review the changes between the branches.&lt;/li&gt;
&lt;li&gt;You can see the list of commits, files that have been changed, added, or deleted, and the specific code differences.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Comparing branches in Azure DevOps helps in maintaining code quality and collaboration within your team. By following these steps, you can easily compare branches, review changes, and make informed decisions about merging.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I hope this helps! If you have any questions or need further assistance, feel free to ask.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Mocking Azure.Data.AppConfiguration.ConfigurationClient using Moq]]></title><description><![CDATA[Introduction When working with Azure’s App Configuration service in .NET, it’s common to use the  class to interact with our configuration…]]></description><link>https://rubberduckdev.com/mocking-configurationclient/</link><guid isPermaLink="false">https://rubberduckdev.com/mocking-configurationclient/</guid><pubDate>Mon, 02 Sep 2024 23:30:00 GMT</pubDate><content:encoded>&lt;h3&gt;Introduction&lt;/h3&gt;
&lt;p&gt;When working with Azure’s App Configuration service in .NET, it’s common to use the &lt;code class=&quot;language-text&quot;&gt;Azure.Data.AppConfiguration.ConfigurationClient&lt;/code&gt; class to interact with our configuration settings. In a unit test scenario, however, interacting directly with the Azure service is not ideal. Instead, we can mock the &lt;code class=&quot;language-text&quot;&gt;ConfigurationClient&lt;/code&gt; using a library like Moq to simulate its behaviour, allowing us to test our code in isolation.&lt;/p&gt;
&lt;p&gt;In this blog post, we’ll explore how to mock the &lt;code class=&quot;language-text&quot;&gt;ConfigurationClient&lt;/code&gt; and simulate the paging behaviour of its &lt;code class=&quot;language-text&quot;&gt;GetConfigurationSettings&lt;/code&gt; method using Moq. This allows us to unit test our code effectively without making actual calls to Azure.&lt;/p&gt;
&lt;h4&gt;Setup&lt;/h4&gt;
&lt;p&gt;Before diving into the code, ensure we have the following packages installed in our project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Azure.Data.AppConfiguration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Moq&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Azure.Core&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;we can install them via NuGet:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dotnet &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; package Azure.Data.AppConfiguration
dotnet &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; package Moq
dotnet &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; package Azure.Core&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Example Code&lt;/h4&gt;
&lt;p&gt;Let’s look at an example where we mock the &lt;code class=&quot;language-text&quot;&gt;GetConfigurationSettings&lt;/code&gt; method of &lt;code class=&quot;language-text&quot;&gt;ConfigurationClient&lt;/code&gt;. We’ll create a mock response that returns a list of &lt;code class=&quot;language-text&quot;&gt;ConfigurationSetting&lt;/code&gt; objects and simulate the paging behaviour.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Arrange&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; endpoint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://example.azure-appconfig.net&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; filter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;KeyFilter&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Empty&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; expectedValues &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Value1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Value2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Value3&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Create a mock of the ConfigurationClient&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; configurationClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Mock&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ConfigurationClient&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Simulate a page of ConfigurationSetting objects&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; configurationSettingPage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Page&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ConfigurationSetting&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FromValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ConfigurationSetting&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ConfigurationSetting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Key1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Value1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ConfigurationSetting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Key2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Value2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ConfigurationSetting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Key3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Value3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token named-parameter punctuation&quot;&gt;continuationToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token named-parameter punctuation&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Mock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Of&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Response&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Create a Pageable response that contains the page&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Pageable&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ConfigurationSetting&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FromPages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Page&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ConfigurationSetting&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; configurationSettingPage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Set up the mock to return the simulated pageable response when GetConfigurationSettings is called&lt;/span&gt;
configurationClient
 &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetConfigurationSettings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;It&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;IsAny&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;SettingSelector&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; It&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;IsAny&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;CancellationToken&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Returns&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Explanation&lt;/h4&gt;
&lt;p&gt;Let’s break down the code step by step:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Arrange the Test Setup&lt;/strong&gt;: We start by defining the endpoint, filter, and label typically used in the &lt;code class=&quot;language-text&quot;&gt;GetConfigurationSettings&lt;/code&gt; method call. We also define the expected values we want our mock to return.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a Mock ConfigurationClient&lt;/strong&gt;: We use Moq to create a mock instance of the &lt;code class=&quot;language-text&quot;&gt;ConfigurationClient&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simulate a Page of Configuration Settings&lt;/strong&gt;: The &lt;code class=&quot;language-text&quot;&gt;Page&amp;lt;ConfigurationSetting&gt;&lt;/code&gt; class represents a single page of results. We use the &lt;code class=&quot;language-text&quot;&gt;FromValues&lt;/code&gt; method to create a page containing three &lt;code class=&quot;language-text&quot;&gt;ConfigurationSetting&lt;/code&gt; objects. Each setting has a key and value pair.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a Pageable Response&lt;/strong&gt;: The &lt;code class=&quot;language-text&quot;&gt;Pageable&amp;lt;T&gt;&lt;/code&gt; class represents a sequence of pages. We use &lt;code class=&quot;language-text&quot;&gt;FromPages&lt;/code&gt; to create a pageable response containing the page we just created. This simulates the response we’d get from the real &lt;code class=&quot;language-text&quot;&gt;ConfigurationClient&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set up the Mock Behavior&lt;/strong&gt;: Finally, we set up the mock &lt;code class=&quot;language-text&quot;&gt;ConfigurationClient&lt;/code&gt; to return our pageable response when the &lt;code class=&quot;language-text&quot;&gt;GetConfigurationSettings&lt;/code&gt; method is called with any &lt;code class=&quot;language-text&quot;&gt;SettingSelector&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;CancellationToken&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;Mocking Azure services like &lt;code class=&quot;language-text&quot;&gt;ConfigurationClient&lt;/code&gt; allows us to isolate our unit tests from external dependencies, ensuring that our tests are fast, reliable, and repeatable. By using Moq and Azure SDK’s built-in paging support, we can simulate complex behaviours like pagination and ensure our code handles these scenarios correctly.&lt;/p&gt;
&lt;p&gt;For more detailed information on mocking with the Azure SDK and Moq, refer to the official &lt;a href=&quot;https://learn.microsoft.com/en-gb/dotnet/azure/sdk/unit-testing-mocking?tabs=moq#explore-paging&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft documentation&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Managing Identity and Role Assignments in Pulumi with C#]]></title><description><![CDATA[TLDR If you need to rename role assignment, rename identity too.
Otherwise deployment will fail with role already exists error as Pulumi…]]></description><link>https://rubberduckdev.com/pulumi-mi-roleassignment-rename/</link><guid isPermaLink="false">https://rubberduckdev.com/pulumi-mi-roleassignment-rename/</guid><pubDate>Mon, 19 Aug 2024 10:30:00 GMT</pubDate><content:encoded>&lt;h2&gt;TLDR&lt;/h2&gt;
&lt;p&gt;If you need to rename role assignment, rename identity too.
Otherwise deployment will fail with role already exists error as Pulumi will try to create new role before deleting old one.
This if &lt;code class=&quot;language-text&quot;&gt;DeleteBeforeReplace&lt;/code&gt; is not already in state. If it is in state, role exists error may not happen.&lt;/p&gt;
&lt;p&gt;If you need to rename identity name, rename Cosmos RBAC role assignments (e.g. DocumentDB.SqlResourceSqlRoleAssignment).
Otherwise issues like “Updating SQL Role Assignment Principal ID is not permitted. You may only update the associated Role Definition.” are likely to happen.&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;When working with infrastructure as code (IaC) tools like Pulumi, particularly when provisioning cloud resources in Azure, handling resource identities and their associated role assignments is critical. A common scenario arises when you need to rename either a resource’s identity or its role assignment. If not handled correctly, this can lead to errors that can disrupt your deployment process. This post will guide you through handling these situations using C# in Pulumi.&lt;/p&gt;
&lt;h3&gt;Problem Overview&lt;/h3&gt;
&lt;p&gt;In Azure, resources like databases, storage accounts, or virtual machines often have associated identities and role assignments. When renaming these, Pulumi may encounter issues if it tries to create new resources before deleting the old ones. This can lead to errors such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Role assignment already exists&lt;/code&gt;: Occurs if Pulumi tries to create a new role assignment before deleting the old one.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Updating SQL Role Assignment Principal ID is not permitted&lt;/code&gt;: Occurs if Pulumi attempts to update certain properties of an existing &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/cosmos-db/role-based-access-control&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Cosmos RBAC&lt;/a&gt; role assignment directly, which Azure does not allow.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Key Concepts&lt;/h3&gt;
&lt;p&gt;Before diving into code examples, let’s clarify a few key concepts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;DeleteBeforeReplace&lt;/strong&gt;: This is a Pulumi option that controls whether Pulumi should delete an existing resource before creating its replacement. When renaming resources, setting this option can help avoid conflicts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Role Assignments&lt;/strong&gt;: These define the permissions for an identity over a particular scope (e.g., a resource group or a database). Azure does not allow modifying the &lt;code class=&quot;language-text&quot;&gt;PrincipalId&lt;/code&gt; (the identity) of an existing role assignment directly; instead, you must delete and recreate the assignment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identity&lt;/strong&gt;: This can be a managed identity in Azure that is assigned to a resource. It is often tied to role assignments, which give it specific permissions.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Handling Renaming Scenarios&lt;/h3&gt;
&lt;h4&gt;1. &lt;strong&gt;Renaming Role Assignment and Identity Simultaneously&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;If you need to rename both role assignment and its associated identity, you must be cautious. Pulumi’s default behavior might try to create the new role assignment before deleting the old one, leading to a “role already exists” error. To avoid this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rename the role assignment&lt;/strong&gt;: Make sure to rename the identity at the same time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use &lt;code class=&quot;language-text&quot;&gt;DeleteBeforeReplace&lt;/code&gt;&lt;/strong&gt;: Ensure that the old role assignment is deleted before the new one is created.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is how you can implement this in Pulumi using C#:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Authorization&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Authorization&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ManagedIdentity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyStack&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Stack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MyStack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; identity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;UserAssignedIdentity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;newIdentity&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;UserAssignedIdentityArgs&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;my-resource-group&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Other identity properties&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; roleAssignment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;RoleAssignment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;newRoleAssignment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;RoleAssignmentArgs&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            PrincipalId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; identity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PrincipalId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            RoleDefinitionId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/{roleDefinitionId}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Scope &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/subscriptions/{subscriptionId}/resourceGroups/my-resource-group&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;CustomResourceOptions&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            DeleteBeforeReplace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example, &lt;code class=&quot;language-text&quot;&gt;DeleteBeforeReplace&lt;/code&gt; is explicitly set to &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;, ensuring that Pulumi deletes the old role assignment before creating the new one.
Note that &lt;code class=&quot;language-text&quot;&gt;DeleteBeforeReplace&lt;/code&gt; needs to already be in Pulumi state for it to take effect. So a rename takes place in the same commit/revision when enabling &lt;code class=&quot;language-text&quot;&gt;DeleteBeforeReplace&lt;/code&gt;, it won’t take effect.&lt;/p&gt;
&lt;h4&gt;2. &lt;strong&gt;Renaming Identity Without Renaming Role Assignments&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;If you only rename the identity but leave the role assignments unchanged, you can run into issues such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Updating SQL Role Assignment Principal ID is not permitted&lt;/code&gt;: This error occurs because Azure does not allow direct updates to the &lt;code class=&quot;language-text&quot;&gt;PrincipalId&lt;/code&gt; of a role assignment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To avoid this, you must also rename the role assignment when renaming the identity:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Authorization&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Authorization&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ManagedIdentity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyStack&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Stack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MyStack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; identity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;UserAssignedIdentity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;updatedIdentity&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;UserAssignedIdentityArgs&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;my-resource-group&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Other identity properties&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Role assignment must be renamed alongside the identity&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; roleAssignment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;DocumentDB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlResourceSqlRoleAssignment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;updatedRoleAssignment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;DocumentDB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlResourceSqlRoleAssignmentArgs&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            PrincipalId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; identity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PrincipalId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            RoleDefinitionId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/{roleDefinitionId}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Scope &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/subscriptions/{subscriptionId}/resourceGroups/my-resource-group&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;CustomResourceOptions&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            DeleteBeforeReplace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, the role assignment is renamed to match the identity’s new name, and &lt;code class=&quot;language-text&quot;&gt;DeleteBeforeReplace&lt;/code&gt; ensures that the transition happens smoothly without encountering errors.&lt;/p&gt;
&lt;h3&gt;Potential Pitfalls&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;State File Considerations&lt;/strong&gt;: If &lt;code class=&quot;language-text&quot;&gt;DeleteBeforeReplace&lt;/code&gt; is already in the state, you might not encounter the “role already exists” error because the resource will be replaced cleanly. However, always double-check your resource states and configurations, especially when working in a collaborative environment where the state might have been modified by others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource Dependencies&lt;/strong&gt;: Ensure that your Pulumi code correctly models dependencies between resources. For instance, role assignments should depend on the existence of the identity, which Pulumi typically handles, but it’s good practice to verify this.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Handling renaming of identities and role assignments in Pulumi requires careful planning to avoid common pitfalls. By using &lt;code class=&quot;language-text&quot;&gt;DeleteBeforeReplace&lt;/code&gt; and ensuring that associated resources like role assignments are renamed together, you can avoid errors and ensure a smooth deployment process. Pulumi’s flexibility with C# allows you to manage these intricacies effectively, enabling reliable infrastructure provisioning in Azure.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Leveraging ephemeral image registry ttl.sh using Azure Pipelines]]></title><description><![CDATA[Introduction When it comes to testing Docker images, having a reliable platform to quickly spin up containers for testing purposes can be…]]></description><link>https://rubberduckdev.com/ttl-sh-azure-pipelines/</link><guid isPermaLink="false">https://rubberduckdev.com/ttl-sh-azure-pipelines/</guid><pubDate>Wed, 10 Apr 2024 17:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;When it comes to testing Docker images, having a reliable platform to quickly spin up containers for testing purposes can be invaluable. Enter &lt;a href=&quot;https://ttl.sh/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;ttl.sh&lt;/a&gt;, a convenient service that allows you to host and test Docker images with ease. In this blog post, we’ll explore how to leverage ttl.sh within Azure Pipelines to streamline your testing workflow.&lt;/p&gt;
&lt;h3&gt;What is ttl.sh?&lt;/h3&gt;
&lt;p&gt;Ttl.sh is a service designed to simplify Docker image testing. It provides a temporary container registry (hence the name “ttl,” which stands for “time to live”) where you can push your Docker images for testing purposes. Once pushed, ttl.sh automatically spins up containers from your images, allowing you to validate their functionality and compatibility. Bear in mind that the image will get purged after 24hrs or less depending on the tag.&lt;/p&gt;
&lt;h3&gt;Setting Up Azure Pipelines with ttl.sh&lt;/h3&gt;
&lt;p&gt;To integrate ttl.sh into your Azure Pipelines workflow, follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create Dummy Credentials in Azure DevOps Service Connection&lt;/strong&gt;: Start by creating a dummy service connection in Azure DevOps. This connection will be used to authenticate and push Docker images to ttl.sh. While the credentials don’t need to be real, ensure they have the necessary permissions to push images to your chosen container registry.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 457px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8679ced8cba490af8057cf90f73cdf83/11b44/service-connection.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 172.6962457337884%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAjCAYAAACU9ioYAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAD4UlEQVRIx51WWXbiRhTVCvKRnxy73TaYQRKD0MwMAkmYebDbeDj9nXxlD1lEVpJ1ZQ83uU8IE5pjB39cvVdS6dWb6lYpry/PeNo+Yvv4TeRyMcdmvcLz0xZP2y34vdNuwawZsC3zB1hmDa5jo9moi66EwwGGg0AQhUOE4RBxFGEyvsP4boTpZIxG3ZfJ7xlM5yiDoI+g3xMvWs0GqmUdRllHuaSjUi4JakZVJtPLY/B9+o0LKAzx28O9hLlezuH4TZj1DtrNOuq+B8914HuugGN6IrKRfE+9TKXCR7fTxnA4QNBtoREtoAcbzMcxoihCu92SVIziGHejGIv5DKM4wmq5lHQw3MN0KAy33+tK2ITvOrCMysnw3gs5hYTMldarJe43azFuW5asfC4c24LCnPS6HQmbhSkW8sjnbs9EDrnbbGKQyWZ12UdMdLrSZ8B/FYZKMGwmlVX1HPt8uE5SFOas1WpKr7HnSuUStHIJ+rko6YmHtMxqMRfFYgF2IY+GqqK+Q+NI7qH9d46rFuHQYDgcyrZjb43CIV77PUx8H1Pfw8z3MPY90VM59z1MKL1kPN2NH3wv8XCxmGMxn2M6nWA1n+El6GNh29jaNl5tG/cOdQuP1G0Lz7t3361Ef7FtPNg21naSR8WoVqRCuqbi5vor2oU8urqGtpago73pb1DR0XffdjLQNLg0SDdZnbTsFsc7OAf6e+/s3X8SMnfHw/1GuJAcGA4COJ9pn7Rt+GDY6d6U1tG1T0E8pKIWC/uX5MHPQgxGYQiydsp7EuoB3KPxKbiHIbMHSVvVSnkPpiBNwzE9fQSFZwfJk/242ayxWa+FRFfLxX5VdsCp8+TU+aIEQR/9fk9+JqUTKeVzf6fe/h9IDlPqIp+RCwv5nEhNLZ5dZdkpnXZbyJV5TCUZKA33HOyPADY3OZG5o75aLeV046SEcD+C9XYEMLzMzTVusxlBNnMjIJ0x/Hw+J/IjcL4YJCnQ0PE5wZyeA9oQg67rSvOec7K9B2V2F2E2nUi+uI+N6kGDHzR5+u6HZmYxLAuWuStKbvobgjBCHA4QRyGiKEQcR3JLkMvTcCD6dDIReXxTcGpluIYOp1ZNtt6t2YJRM6Va+xbYuZ9eguiFK21kCZEQLKaqFpExO7hyYmTLDiolDYpb78FpR3C7Y+TyKi4vfsHVl0t8ubwQmYJjgslnFJvVAqZbx8+//4Xsn3/jp9UfKOW+QjGcFrxODK87guE0cXN9hUzmRlrpFNhSpCqzVkWhWMRl8x6Z8a+4qAUoqXko1UpJ7oSCSnIXfA/G7vymURatpudhqFnUyirMf4vyDwu7WrHRZcK8AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;architecture&quot;
        title=&quot;architecture&quot;
        src=&quot;/static/8679ced8cba490af8057cf90f73cdf83/11b44/service-connection.png&quot;
        srcset=&quot;/static/8679ced8cba490af8057cf90f73cdf83/8890b/service-connection.png 293w,
/static/8679ced8cba490af8057cf90f73cdf83/11b44/service-connection.png 457w&quot;
        sizes=&quot;(max-width: 457px) 100vw, 457px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;br/&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Configure Your Pipeline&lt;/strong&gt;: Below is a basic example of an Azure Pipelines YAML configuration that builds a Docker image and pushes it to ttl.sh:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; main

&lt;span class=&quot;token key atrule&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;vmImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest

&lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Docker@2
  &lt;span class=&quot;token key atrule&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;containerRegistry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ttlsh&apos;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2cc2bc2c-cc5d-4c47-81ed-6ea9aaead977&apos;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;buildAndPush&apos;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;Dockerfile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./Dockerfile&apos;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;8h&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The pipeline triggers on changes to the &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;It utilizes an Ubuntu latest image.&lt;/li&gt;
&lt;li&gt;The Docker task builds the image using the specified Dockerfile and pushes it to the ttl.sh registry. The &lt;code class=&quot;language-text&quot;&gt;tags&lt;/code&gt; parameter specifies how long the image should be available on ttl.sh before expiring.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customize as Needed&lt;/strong&gt;: Customize the pipeline according to your project’s requirements. You can add additional steps for testing, deployment, or any other tasks necessary for your workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Integrating ttl.sh into your Azure Pipelines workflow provides a convenient solution for testing Docker images. By following the steps outlined above, you can easily push Docker images to ttl.sh and validate their functionality within temporary containers. This streamlined testing process helps ensure the reliability and compatibility of your Docker images before deploying them to production environments. Happy testing!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Simplifying Resource Management With Parent Option in Pulumi]]></title><description><![CDATA[Introduction In the world of infrastructure as code (IaC), managing dependencies and relationships between resources is crucial for…]]></description><link>https://rubberduckdev.com/pulumi-parent-option/</link><guid isPermaLink="false">https://rubberduckdev.com/pulumi-parent-option/</guid><pubDate>Thu, 28 Mar 2024 23:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In the world of infrastructure as code (IaC), managing dependencies and relationships between resources is crucial for maintaining an efficient and organized infrastructure. Pulumi, a powerful IaC tool, offers a feature called the “Parent Option” to streamline resource management within its ecosystem. In this post, we’ll explore the Parent Option in Pulumi and understand how it simplifies resource orchestration through a practical example.&lt;/p&gt;
&lt;h3&gt;What is the Parent Option?&lt;/h3&gt;
&lt;p&gt;The Parent Option in Pulumi allows developers to specify the parent-child relationship between resources. When creating resources within Pulumi, specifying a parent resource helps in organizing and managing dependencies effectively. By defining parent-child relationships, Pulumi can automate resource lifecycle management, ensuring proper ordering of resource creation, updates, and deletion.&lt;/p&gt;
&lt;h3&gt;Practical Example: Custom Storage Account&lt;/h3&gt;
&lt;p&gt;Let’s consider a scenario where we need to create a custom storage account in Azure using Pulumi. We’ll create a custom component resource called &lt;code class=&quot;language-text&quot;&gt;CustomStorageAccount&lt;/code&gt; that encapsulates the creation of a storage account. Initially, we’ll create the resource without specifying a parent, and then we’ll refactor the code to utilize the Parent Option.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomStorageAccount&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;ComponentResource&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CustomStorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;CustomStorageAccountArgs&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ComponentResourceOptions&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;CommonComponent:CustomStorageAccount&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Creating storage account without specifying a parent&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccountArgs&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ResourceGroupName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Sku &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SkuArgs&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SkuName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Standard_LRS
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Kind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StorageV2&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Tags &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;InputMap&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Purpose&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Purpose &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Owner&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Owner &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When previewing this configuration, resources will be created independently, without any parent-child relationship:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;pulumi preview --stack stacknamedev01
Previewing update (stacknamedev01):
     Type                                     Name                            Plan
 +   pulumi:pulumi:Stack                      stackname-stacknamedev01  create
 +   ├─ CommonComponent:CustomStorageAccount  sa                              create
 +   ├─ azure-native:resources:ResourceGroup  resourceGroup                   create
 +   └─ azure-native:storage:StorageAccount   sa                              create
Resources:
    + 4 to create&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Utilizing the Parent Option&lt;/h3&gt;
&lt;p&gt;Now, let’s refactor the code to utilize the Parent Option by specifying the component resource (&lt;code class=&quot;language-text&quot;&gt;CustomStorageAccount&lt;/code&gt;) as the parent for the storage account resource. Note the &lt;code class=&quot;language-text&quot;&gt;new CustomResourceOptions { Parent = this }&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomStorageAccount&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;ComponentResource&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CustomStorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;CustomStorageAccountArgs&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ComponentResourceOptions&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;CommonComponent:CustomStorageAccount&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Creating storage account with parent specified&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccountArgs&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ResourceGroupName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Sku &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SkuArgs&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SkuName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Standard_LRS
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Kind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StorageV2&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            Tags &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;InputMap&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Purpose&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Purpose &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Owner&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Owner &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;CustomResourceOptions&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Parent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When previewing this configuration, the parent-child relationship is correctly inferred, and resources will be organized accordingly:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;pulumi preview --stack stacknamedev01
Previewing update (stacknamedev01):
     Type                                       Name                            Plan
 +   pulumi:pulumi:Stack                        stackname-stacknamedev01  create
 +   ├─ CommonComponent:CustomStorageAccount    sa                              create
 +   │  └─ azure-native:storage:StorageAccount  sa                              create
 +   └─ azure-native:resources:ResourceGroup    resourceGroup                   create
Resources:
    + 4 to create&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The Parent Option in Pulumi simplifies resource management by allowing developers to define parent-child relationships between resources. This feature enhances the organization and maintainability of infrastructure code, leading to more robust and scalable infrastructure deployments.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Resolving NU1301 when using Azure Artifacts]]></title><description><![CDATA[Understanding the NU1301 Error Before we delve into the solution, let’s briefly understand what the NU1301 error signifies. This error…]]></description><link>https://rubberduckdev.com/restore-NU1301-error/</link><guid isPermaLink="false">https://rubberduckdev.com/restore-NU1301-error/</guid><pubDate>Wed, 14 Feb 2024 14:30:00 GMT</pubDate><content:encoded>&lt;h3&gt;Understanding the NU1301 Error&lt;/h3&gt;
&lt;p&gt;Before we delve into the solution, let’s briefly understand what the NU1301 error signifies. This error message typically occurs when the NuGet package manager is unable to load the service index for a particular package source. It can happen due to various reasons, such as network connectivity issues, misconfigured package sources, or authentication problems. In this post we will look to solve the authentication problem.&lt;/p&gt;
&lt;h3&gt;Step 1: Install the Azure Artifacts Credential Provider&lt;/h3&gt;
&lt;p&gt;The Azure Artifacts Credential Provider is a tool that helps manage credentials for Azure Artifact based NuGet package sources. To install this provider, follow &lt;a href=&quot;https://github.com/microsoft/artifacts-credprovider?tab=readme-ov-file#setup&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Step 2: Run &lt;code class=&quot;language-text&quot;&gt;dotnet restore --interactive&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Once installed, run the &lt;code class=&quot;language-text&quot;&gt;dotnet restore --interactive&lt;/code&gt; command to resolve the NU1301 error. This command initiates an interactive authentication process, allowing us to provide credentials for the NuGet package sources that require authentication. Follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to your .NET project directory using the command prompt or terminal.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the following command:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;dotnet restore --interactive&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;When prompted, enter the required credentials for the NuGet package sources. Make sure to provide accurate credentials to authenticate successfully.&lt;/li&gt;
&lt;li&gt;Once the authentication process is complete, the &lt;code class=&quot;language-text&quot;&gt;dotnet restore&lt;/code&gt; command will proceed to restore the NuGet packages for your project without encountering the NU1301 error.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This post is more like a note to self and I hope it helps out if others get this error. Feel free to reach out if you have any questions or encounter any issues along the way. Happy coding.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Unity Android - Resolve APK Failed to Install]]></title><description><![CDATA[As a newcomer to the world of Android development and Unity, encountering errors can be a daunting experience. One common stumbling block…]]></description><link>https://rubberduckdev.com/unity-android-no-matching-abis/</link><guid isPermaLink="false">https://rubberduckdev.com/unity-android-no-matching-abis/</guid><pubDate>Sun, 11 Feb 2024 22:30:00 GMT</pubDate><content:encoded>&lt;p&gt;As a newcomer to the world of Android development and Unity, encountering errors can be a daunting experience. One common stumbling block that many beginners face is the dreaded “APK failed to install” error, accompanied by the message: “INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113.” If you’ve encountered this error, fear not! In this guide, we’ll walk through a simple solution that involves adjusting some settings in Unity to resolve the issue.&lt;/p&gt;
&lt;p&gt;Before we dive into the solution, let’s first understand what this error means. When we build our Unity project for Android, it includes native libraries specific to the CPU architecture of the target device. These native libraries are compiled into our APK and are essential for our app to run correctly on different devices. The error message “INSTALL_FAILED_NO_MATCHING_ABIS” indicates that the Android Package (APK) does not contain native libraries compatible with the CPU architecture of the device on which we are trying to install the app.&lt;/p&gt;
&lt;p&gt;To fix this error, follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open our Unity Project:&lt;/strong&gt;
Launch Unity and open the project in which we are encountering the “APK failed to install” error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Navigate to Player Settings:&lt;/strong&gt;
In the Unity Editor, go to &lt;code class=&quot;language-text&quot;&gt;Edit &gt; Project Settings &gt; Player&lt;/code&gt; to access the Player Settings for our project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set Scripting Backend to IL2CPP:&lt;/strong&gt;
In the Player Settings window, locate the “Other Settings” section. Under this section, find the “Scripting Backend” dropdown menu and select “IL2CPP.” IL2CPP is a Unity technology that translates our C# scripts into C++ code, which can then be compiled natively for the target platform, including Android.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Select ARMv7 and ARM64 Architectures:&lt;/strong&gt;
Still in the Player Settings window, scroll down to the “Target Architectures” section. Check the boxes next to “ARMv7” and “ARM64” architectures. These options ensure that our APK includes native libraries compatible with both ARMv7 and ARM64 CPU architectures, covering a wide range of Android devices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rebuild our Project:&lt;/strong&gt;
After making these changes, rebuild our Unity project for Android by going to &lt;code class=&quot;language-text&quot;&gt;File &gt; Build Settings &gt; Build&lt;/code&gt;. Ensure that we select the appropriate platform (Android) and click on “Build.” This process will generate a new APK with the updated settings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test our App:&lt;/strong&gt;
Once the build process is complete, install the newly generated APK on our device and verify that the “APK failed to install” error no longer occurs. our app should now install and run successfully on a variety of Android devices.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By setting the scripting backend to IL2CPP and selecting ARMv7 and ARM64 architectures in the Unity Player Settings, we’ve ensured that our APK includes the necessary native libraries to support a wide range of Android devices. This simple adjustment resolves the “INSTALL_FAILED_NO_MATCHING_ABIS” error and paves the way for a smoother development experience.&lt;/p&gt;
&lt;p&gt;Remember, as a newcomer to Android and Unity development, I am learning this solution among few others. This post is mainly for me not to forget this lesson and hopefully it helps ou out other newbies as well. Happy coding!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using Pulumi's Apply Method in C#]]></title><description><![CDATA[Introduction Pulumi, a powerful Infrastructure as Code (IaC) tool, empowers developers to define, deploy, and manage cloud infrastructure…]]></description><link>https://rubberduckdev.com/pulumi-apply/</link><guid isPermaLink="false">https://rubberduckdev.com/pulumi-apply/</guid><pubDate>Thu, 11 Jan 2024 10:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Pulumi, a powerful Infrastructure as Code (IaC) tool, empowers developers to define, deploy, and manage cloud infrastructure using familiar programming languages such as C#. One of the key features that sets Pulumi apart is its &lt;code class=&quot;language-text&quot;&gt;Apply&lt;/code&gt; method, which allows for dynamic configuration and transformation of resources during deployment. In this blog post, we’ll explore the &lt;code class=&quot;language-text&quot;&gt;Apply&lt;/code&gt; method and provide examples of its usage in C# with Pulumi.&lt;/p&gt;
&lt;h2&gt;Understanding the Apply Method&lt;/h2&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;Apply&lt;/code&gt; method in Pulumi serves as a bridge between resource creation and configuration. It enables developers to apply transformations and logic to the attributes of a resource before or after its creation. This is particularly useful when you need to dynamically set properties based on other resources or external inputs.&lt;/p&gt;
&lt;h3&gt;Syntax:&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;TResource.Apply&amp;lt;TResourceType&gt;(Func&amp;lt;TResourceType, TResourceType&gt; transformation)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, &lt;code class=&quot;language-text&quot;&gt;TResource&lt;/code&gt; represents the type of the resource, and &lt;code class=&quot;language-text&quot;&gt;TResourceType&lt;/code&gt; represents the type of the resource’s properties.&lt;/p&gt;
&lt;h2&gt;Examples of Apply Method Usage&lt;/h2&gt;
&lt;p&gt;Here are a couple of examples demonstrating the usage of the &lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt; method in C# with Pulumi:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Concatenating Strings:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;using Pulumi;

class MyStack : Stack
{
    public MyStack()
    {
        var prefix = &quot;Hello, &quot;;
        var name = &quot;Pulumi&quot;;

        var greeting = Output.Create($&quot;{prefix}&quot;).Apply(p =&gt; p + name);

        var myResource = new SomeResource(&quot;exampleResource&quot;, new SomeResourceArgs
        {
            Message = greeting,
            // other properties...
        });
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example, the &lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt; method is used to concatenate the &lt;code class=&quot;language-text&quot;&gt;prefix&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;name&lt;/code&gt; strings to create a dynamic greeting.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Conditionally Setting Values:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;using Pulumi;

class MyStack : Stack
{
    public MyStack()
    {
        var isProduction = true;

        var environment = Output.Create(&quot;development&quot;).Apply(p =&gt; isProduction ? &quot;production&quot; : p);

        var myResource = new SomeResource(&quot;exampleResource&quot;, new SomeResourceArgs
        {
            Environment = environment,
            // other properties...
        });
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, &lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt; is employed to conditionally set the &lt;code class=&quot;language-text&quot;&gt;environment&lt;/code&gt; variable based on the value of &lt;code class=&quot;language-text&quot;&gt;isProduction&lt;/code&gt;. This allows for flexible resource configuration depending on the deployment context.&lt;/p&gt;
&lt;p&gt;These examples showcase how the &lt;code class=&quot;language-text&quot;&gt;apply&lt;/code&gt; method in Pulumi enables dynamic and expressive infrastructure configurations in C#.&lt;/p&gt;
&lt;p&gt;These examples demonstrate how the &lt;code class=&quot;language-text&quot;&gt;Apply&lt;/code&gt; method can be applied to Azure cloud operations and regular string operations, providing a flexible and dynamic approach to infrastructure as code in Pulumi.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;Apply&lt;/code&gt; method in Pulumi is a powerful tool for dynamic resource configuration and transformation. By leveraging this method, developers can write more flexible and dynamic infrastructure code, making it easier to adapt to changing requirements and environments. These examples showcase just a glimpse of the capabilities that Pulumi’s &lt;code class=&quot;language-text&quot;&gt;Apply&lt;/code&gt; method brings to the table, offering a seamless way to customize your infrastructure deployments in C#. For more details visit &lt;a href=&quot;https://www.pulumi.com/docs/concepts/inputs-outputs/#apply&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Pulumi Docs&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Azure Pipelines Artifact Download Issue - A Quick Fix]]></title><description><![CDATA[The Problem: There is a peculiar behavior when downloading artifacts using Azure Pipelines. The artifact, when viewed on the repository…]]></description><link>https://rubberduckdev.com/azure-pipelines-download-task-anomaly/</link><guid isPermaLink="false">https://rubberduckdev.com/azure-pipelines-download-task-anomaly/</guid><pubDate>Mon, 08 Jan 2024 10:30:00 GMT</pubDate><content:encoded>&lt;h2&gt;The Problem:&lt;/h2&gt;
&lt;p&gt;There is a peculiar behavior when downloading artifacts using Azure Pipelines. The artifact, when viewed on the repository, displayed a seemingly correct structure. However, upon downloading, an unexpected string was prefixed to every element, causing a disruption in the intended folder structure and file names.&lt;/p&gt;
&lt;p&gt;For a detailed look into the issue, check out the GitHub thread &lt;a href=&quot;https://github.com/microsoft/azure-pipelines-tasks/issues/16522&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Understanding the Solution:&lt;/h2&gt;
&lt;p&gt;My co-worker &lt;a href=&quot;https://www.linkedin.com/in/daniel-ponting&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Dan Ponting&lt;/a&gt; found the issue as well as shared this solution with me, the details are below.&lt;/p&gt;
&lt;p&gt;The standard way of calling the task, as shown below, led to the unwanted manipulation of folder structures and file names:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; DownloadPipelineArtifact@2
  &lt;span class=&quot;token key atrule&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;artifactName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;MyFavouriteArtifact&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;The Solution:&lt;/h2&gt;
&lt;p&gt;The workaround involves using the shorthand version of the task, which remarkably eliminates the issue. Instead of the conventional method, the user suggests opting for the following configuration:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;download&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; current
  &lt;span class=&quot;token key atrule&quot;&gt;artifact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; MyFavouriteArtifact&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This simple adjustment in the way the task is invoked resolves the problem, ensuring that the downloaded artifact retains the intended structure without any unexpected string prefixes.&lt;/p&gt;
&lt;h2&gt;Conclusion:&lt;/h2&gt;
&lt;p&gt;The solution was easier to locate all thanks to GitHub and my coworker. The shared knowledge and experiences within the community contribute significantly to the overall improvement and efficiency of DevOps processes. Hopefully this post saves you some time as well.&lt;/p&gt;
&lt;p&gt;For more information on the Azure Pipelines task and its configurations, refer to the official documentation &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/download-pipeline-artifact-v2?view=azure-pipelines&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Making an Azure Pipeline Stage Non-Cancellable]]></title><description><![CDATA[Introduction In a recent StackOverflow post, I sought advice on making a specific stage in an Azure Pipeline non-cancellable. This blog…]]></description><link>https://rubberduckdev.com/azure-pipelines-noncancellable-job/</link><guid isPermaLink="false">https://rubberduckdev.com/azure-pipelines-noncancellable-job/</guid><pubDate>Thu, 28 Dec 2023 10:30:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In a recent &lt;a href=&quot;https://stackoverflow.com/questions/77659044/make-azure-pipeline-stage-noncancellable/77659133&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;StackOverflow post&lt;/a&gt;, I sought advice on making a specific stage in an Azure Pipeline non-cancellable. This blog explores the question and summarizes the solutions proposed on the post as well as my co-worker &lt;a href=&quot;https://www.linkedin.com/in/daniel-ponting&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Dan&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;The Challenge&lt;/h3&gt;
&lt;p&gt;Developers often face the challenge of ensuring the reliability of each stage in an Azure Pipeline. My specific concern was making a stage non-cancellable, meaning it should not be interrupted once started.&lt;/p&gt;
&lt;h2&gt;Understanding Azure Pipelines&lt;/h2&gt;
&lt;p&gt;Azure Pipelines involve stages, each comprising one or more jobs. Jobs represent phases with multiple tasks. My focus was on enforcing a non-cancellable behavior for a particular stage.&lt;/p&gt;
&lt;h2&gt;Proposed Solution&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;Custom Conditions and PowerShell Scripts&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Use &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#job-status-functions&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;always()&lt;/a&gt; to control stage behavior. A condition that always evaluates to true ensures a stage runs, making it non-cancellable.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;stages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; CustomConditionStage
  &lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; CustomConditionJob
    &lt;span class=&quot;token key atrule&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always()
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;powershell&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
        Write-Host &quot;Running CustomConditionStage&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Making an Azure Pipeline stage non-cancellable is important in cases where we share use shared resources. Using Pulumi or Terraform state file backend storage for example or running tests on real cloud resources. In such cases we need to ensure such stages complete successfully and finish their cleanup before next one starts. In such cases the &lt;code class=&quot;language-text&quot;&gt;always()&lt;/code&gt; function and &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/devops/pipelines/process/approvals?view=azure-devops&amp;#x26;preserve-view=true&amp;#x26;tabs=check-pass#exclusive-lock&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;exclusive locks&lt;/a&gt; are really handy.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Pass app configuration via docker entrypoint]]></title><description><![CDATA[Introduction There is a nice post on the many available ways of passing in application configuration into a docker container. Here is…]]></description><link>https://rubberduckdev.com/app-config-via-entrypoint/</link><guid isPermaLink="false">https://rubberduckdev.com/app-config-via-entrypoint/</guid><pubDate>Sun, 09 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;There is a &lt;a href=&quot;https://dantehranian.wordpress.com/2015/03/25/how-should-i-get-application-configuration-into-my-docker-containers/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;nice post&lt;/a&gt; on the many available ways of passing in application configuration into a docker container. Here is another one, which is similar to the ones listed in the post but still possibly stands out as a different technique. &lt;/p&gt;
&lt;h1&gt;App config via entrypoint&lt;/h1&gt;
&lt;p&gt;The idea is that we pass in environment variables to a script that runs as the container &lt;a href=&quot;https://docs.docker.com/engine/reference/builder/#entrypoint&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;entrypoint&lt;/a&gt;. This &lt;code class=&quot;language-text&quot;&gt;entrypoint&lt;/code&gt; script generates a settings JSON file for the application to use as its configuration.&lt;/p&gt;
&lt;p&gt;Docker file with nginx base (can be any other base, but this example is nginx specific)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;FROM nginx:1.23.4-alpine-slim
COPY ./ /src/app/
WORKDIR /src/app
RUN chmod +x settings.sh
ENTRYPOINT [&quot;sh&quot;,&quot;/src/app/settings.sh&quot;]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;settings.sh&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;#!/bin/sh
# expecting these variables are set via environment
echo &quot;{ \&quot;SETTING1\&quot;: \&quot;$SETTING1\&quot;, \&quot;SETTING2\&quot;: \&quot;$SETTING2\&quot; }&quot; &gt; settings.json

# display file to screen for verification
cat settings.json

echo &quot;running nginx command&quot;
set -e
exec nginx -g &quot;daemon off;&quot;
echo &quot;finished running nginx command&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;exec nginx -g &quot;daemon off;&quot;&lt;/code&gt; is the crucial bit. As we are providing an entrypoint, the entrypoint of the base image won’t run. So we need to do what we expect out of the base.&lt;/p&gt;
&lt;p&gt;Docker build, say we call this image &lt;code class=&quot;language-text&quot;&gt;dockerpoc&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;docker build -t dockerpoc:1.0 --no-cache .&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, we pass in the required values via environment variables during the docker run.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;docker run -e SETTING1=&apos;setting1-value&apos; -e SETTING2=&apos;setting2-value&apos; -it dockerpoc:1.0 &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This creates the configuration for the application in a JSON format at the docker container runtime.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saves you some time. Please do share your learnings. If you have any thoughts or comments, please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;
&lt;h3&gt;Credits&lt;/h3&gt;
&lt;p&gt;Banner by &lt;a href=&quot;https://unsplash.com/@rubaitulazad&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Rubaitul Azad&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Unix line endings on Windows]]></title><description><![CDATA[Introduction Many of us are likely running Linux-based container images on the Windows operating system. This is where writing scripts in a…]]></description><link>https://rubberduckdev.com/unix-line-ending-win/</link><guid isPermaLink="false">https://rubberduckdev.com/unix-line-ending-win/</guid><pubDate>Sat, 08 Jul 2023 23:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Many of us are likely running Linux-based container images on the Windows operating system. This is where writing scripts in a Windows IDE such as VSCode and then running them in a container image such as Alpine or Debian becomes troublesome due to encoding and line endings. I recently hit this line-ending issue and took a while to understand the following. There are two types of line endings based on the two main operating systems. Linux and Mac OS X systems use the Unix line ending, which is LF. Windows systems use the DOS line ending, which is CR+LF. When we have the Windows-style line ending and try to run the script in a Linux-based docker container, it ends up throwing errors.&lt;/p&gt;
&lt;h1&gt;Workaround/Solution&lt;/h1&gt;
&lt;p&gt;The workaround I used was to install &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/wsl/install&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;WSL&lt;/a&gt; Ubuntu. This is so that I can test run any shell scripts I write before running them in a docker container.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;wsl --install Ubuntu-22.04&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Install a tool called &lt;a href=&quot;https://dos2unix.sourceforge.io/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;dos2unix&lt;/a&gt;, and it has a detailed &lt;a href=&quot;https://manpages.org/dos2unix&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;man page&lt;/a&gt; as well.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;rubberduck@MYUNIXMACHINE:/mnt/c$sudo apt-get update
rubberduck@MYUNIXMACHINE:/mnt/c$sudo apt-get install dos2unix&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, use the dos2unix tool to change the encodings of the script (say script.sh as an example) so it can run on Linux.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;rubberduck@MYUNIXMACHINE:/mnt/c$ cd source
rubberduck@MYUNIXMACHINE:/mnt/c$ dos2unix script.sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;There might be other better ways of achieving this, and I am still learning the Unix ways of life. If you are aware of an easier technique, please share.
Hope this was useful and saves you some time. Please do share your learnings. If you have any thoughts or comments, please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;
&lt;h3&gt;Credits&lt;/h3&gt;
&lt;p&gt;Banner by &lt;a href=&quot;https://unsplash.com/@6heinz3r&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Gabriel Heinzer&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Azure pipelines - extending yaml array]]></title><description><![CDATA[Introduction Azure Pipelines is a continuous integration and continuous delivery (CI/CD) service that helps you automate the building…]]></description><link>https://rubberduckdev.com/extend-yaml-array/</link><guid isPermaLink="false">https://rubberduckdev.com/extend-yaml-array/</guid><pubDate>Mon, 29 May 2023 15:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started/what-is-azure-pipelines?view=azure-devops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Pipelines&lt;/a&gt; is a continuous integration and continuous delivery (CI/CD) service that helps you automate the building, testing, and deploying of your code to any target environment. I like using the yaml pipelines so that we are practising true &lt;a href=&quot;https://about.gitlab.com/topics/gitops/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GitOps&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Overall scenario&lt;/h1&gt;
&lt;p&gt;In a bid to reduce repetition, it is a good idea to create and share &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure pipeline yaml templates&lt;/a&gt;. Let’s say we have a template as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;parameters:
    - name: dockerPushDependency
      type: object
      default: []  
  stages:
    - stage: DockerBuild
      pool: AgentPoolWithDockerInstance
      jobs:
        - job: DockerBuild
          steps:
          - task: Docker@2
            name: DockerBuild
            displayName: Build image to container registry
            inputs:
              repository: MyRepository
              command: Build
              containerRegistry: MyContainerRegistry
              tags: tag1

    - stage: DockerImageScan
      # ignore details, the built image gets scanned here for known vulnerabilities

    - stage: DockerPush
      dependsOn:
      # needs to depend on parameters and the image scan stage

      pool: AgentPoolWithDockerInstance
      jobs:
        - job: DockerPush
          steps:
          - task: Docker@2
            name: DockerPush
            displayName: Push the image to the container registry
            inputs:
              repository: MyRepository
              command: Push
              containerRegistry: MyContainerRegistry
              tags: tag1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In our template, we have 3 stages. We pass in dependencies into the template so that the docker push can check a previous step, say a test stage, has been completed before it can trigger.
Complexity arises regarding the dependency of DockerPush as it needs to depend on an array of stages that have been passed in as well as a known DockerImageScan stage which is within the template. How do we merge or add this value into the provided array in Azure pipelines yaml?&lt;/p&gt;
&lt;h1&gt;The solution&lt;/h1&gt;
&lt;p&gt;The solution that worked is based on a &lt;a href=&quot;https://stackoverflow.com/a/66894872/1228479&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;great idea provided on Stackoverflow&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;- stage: DockerPush
      dependsOn:
      - ${{ each dependencyItem in parameters.dockerPushDependency }}:
        - ${{ dependencyItem }}
        - DockerImageScan&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Instead of performing any array manipulation, we just loop through it and present the values to the &lt;code class=&quot;language-text&quot;&gt;DockerPush&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;dependsOn&lt;/code&gt; parameter. This stage can now depend on whatever the consumer has provided as well as the known stage within the pipeline template.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Although the trick is quite simple, took hours to get to. Mainly the feedback cycle is slow with Azure pipelines. There is hope though, given such a feature is in &lt;a href=&quot;https://developercommunity.visualstudio.com/t/ability-to-test-yaml-builds-locally/366517&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;preview now&lt;/a&gt;.
Overall, hope this post helps you out with Azure pipelines and happy CI CD to you. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Azure container app - Kestrel - Http 431]]></title><description><![CDATA[Introduction This post is about an error over which I spent hours trying to get anywhere until my colleague Sebastian Wiejas spotted the…]]></description><link>https://rubberduckdev.com/ca-431-cors/</link><guid isPermaLink="false">https://rubberduckdev.com/ca-431-cors/</guid><pubDate>Sat, 27 May 2023 23:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This post is about an error over which I spent hours trying to get anywhere until my colleague &lt;a href=&quot;https://www.linkedin.com/in/sebastian-wiejas-35a4771b2/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Sebastian Wiejas&lt;/a&gt; spotted the issue and suggested the solution.&lt;/p&gt;
&lt;h1&gt;Overall scenario&lt;/h1&gt;
&lt;p&gt;We have a dotnet api deployed as a docker image which is hosted on &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/container-apps/overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Container Apps&lt;/a&gt; instance. But whenever we would try to hit an endpoint, from the UI app (which is hosted as a separate) app, we will get the following error&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Request URL: https://testapi.containerapp.io/api/v1/endpoint1
Request Method: OPTIONS
Status Code: 431 
Remote Address: 25.86.295.132:443
Referrer Policy: strict-origin-when-cross-origin&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Further investigating into Http 431 status code, it says &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;431 Request Header Fields Too Large&lt;/a&gt;, which implies we are sending too many headers or something is limiting our allowed header count.&lt;/p&gt;
&lt;h1&gt;The solution or maybe the workaround&lt;/h1&gt;
&lt;p&gt;Given it is a dotnet app and we know from the deployment image that it is using &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-7.0&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;kestrel web server&lt;/a&gt;, so we investigated further into Kestrel settings.&lt;/p&gt;
&lt;p&gt;One of the settings that stood out was &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.server.kestrel.core.kestrelserverlimits.maxrequestheadercount?view=aspnetcore-7.0&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;KestrelServerLimits.MaxRequestHeaderCount Property&lt;/a&gt;. Although it defaults to 100, in our case, as it turns out it was set to a low number (I think 10) in the app’s &lt;code class=&quot;language-text&quot;&gt;appsettings.json&lt;/code&gt;. So the way we got the app to work is to allow more headers by setting the &lt;code class=&quot;language-text&quot;&gt;MaxRequestHeaderCount&lt;/code&gt; property in &lt;code class=&quot;language-text&quot;&gt;appsettings.json&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
  &quot;Kestrel&quot;: {
    &quot;Endpoints&quot;: {
      &quot;Https&quot;: {
        &quot;Url&quot;: &quot;https://*:443&quot;,
        &quot;Certificate&quot;: {
        }
      },
      &quot;Http&quot;: {
        &quot;Url&quot;: &quot;http://*:80&quot;
      }
    },
    &quot;Limits&quot;: {
      &quot;MaxRequestHeaderCount&quot;: 50 // Default is 100
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saved you some time. And again many thanks to Sebastian for sharing these details. Overall, the app being deployed as a container on Azure container app doesn’t have anything to do with the issue. It was more the server settings which broke &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;cors&lt;/a&gt; requirements.
Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Dependabot GitHub workflow fails - deployment_token was not provided]]></title><description><![CDATA[Introduction The automated update tool Dependabot is a very helpful bot and it creates many fixes in the form of pull requests. Recently I…]]></description><link>https://rubberduckdev.com/dependabot-specific-secrets/</link><guid isPermaLink="false">https://rubberduckdev.com/dependabot-specific-secrets/</guid><pubDate>Mon, 16 Jan 2023 15:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;The automated update tool &lt;a href=&quot;https://github.com/dependabot&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Dependabot&lt;/a&gt; is a very helpful bot and it creates many fixes in the form of pull requests. Recently I learnt why the dependabot pull requests were failing with an error&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;deployment_token was not provided.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;The setup and issue&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/realrubberduckdev/dp-blog/blob/a3e2177a93888f4102b2025c350a77b6a84b7817/.github/workflows/azure-static-web-apps-delightful-glacier-02eccb203.yml&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;github workflow&lt;/a&gt; is deploying an &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/static-web-apps/overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure static webapp&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;- name: Deploy
        id: deploy
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_DELIGHTFUL_GLACIER_02ECCB203 }}
          repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
          action: &quot;upload&quot;
          ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
          # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
          app_location: &quot;/public&quot; # App source code path
          skip_app_build: true
          skip_api_build: true
          ###### End of Repository/Build Configurations ######&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This means that it needs a valid access token to be able to create resources on Azure during the workflow execution. And it is unable to find that secret and hence the error:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;deployment_token was not provided.
The deployment_token is required for deploying content. If you&apos;d like to continue the run without deployment, add the configuration skip_deploy_on_missing_secrets set to true in your workflow file
An unknown exception has occurred&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;The solution&lt;/h1&gt;
&lt;p&gt;Took me a few search attempts to locate &lt;a href=&quot;https://github.com/Azure/static-web-apps/issues/788#issuecomment-1216570180&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;this comment on GitHub&lt;/a&gt;. Essentially, dependabot doesn’t use the default set of secrets, rather we need to explicitly provide a set of secrets for it to use. It makes sense, we don’t want an app sitting outside of our repo to have access to repo/organization secrets.&lt;/p&gt;
&lt;p&gt;The solution is to provide the required secrets for dependabot to use:&lt;/p&gt;
&lt;/br&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b27c19c525b7bca444aee43e0de7b6fe/f5adb/dependabot-secrets.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.85324232081911%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAABJ0AAASdAHeZh94AAAA9ElEQVQoz5XQ6WrDMAwA4P5ffMnW4SuJW3cHhbJX2Pu/1EiysQXKyEAYfHySpZMNyYbkQgIqwNVhtpgAs+Qz50ZxRC6DJeV4MORKju89PM8KRIOclOPlwpLxEeM0WBoMWsy1vcbaOTeH2XMFKtYn6q193OXet3Q/2IZU5pfFG3SY03SVcg4yApUNLx6SUaI0b2qHOTcNMjg2IIVykHELv65AZflFHEOaHlTm3NR6qh3j2tXD2MgOA5V2vdmQvsbz/eKP+DWwkDzXbft0QO6wwzz3Wxq7hoiO9HGsHBsfpV48FwXCa9v/qSxTv7xVTI6LDvFIz59rIS/WA4T4VAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;secrets for dependabot&quot;
        title=&quot;secrets for dependabot&quot;
        src=&quot;/static/b27c19c525b7bca444aee43e0de7b6fe/463a7/dependabot-secrets.png&quot;
        srcset=&quot;/static/b27c19c525b7bca444aee43e0de7b6fe/8890b/dependabot-secrets.png 293w,
/static/b27c19c525b7bca444aee43e0de7b6fe/1f316/dependabot-secrets.png 585w,
/static/b27c19c525b7bca444aee43e0de7b6fe/463a7/dependabot-secrets.png 1170w,
/static/b27c19c525b7bca444aee43e0de7b6fe/f5adb/dependabot-secrets.png 1544w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;/br&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saved you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why is Azure firewall allowing a request that should be denied?]]></title><description><![CDATA[Introduction Azure Firewall is an exciting cloud native network security service and provides a lot of features to make our life easier…]]></description><link>https://rubberduckdev.com/firewall-allowing-unauthorized-request/</link><guid isPermaLink="false">https://rubberduckdev.com/firewall-allowing-unauthorized-request/</guid><pubDate>Sat, 14 Jan 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/firewall/overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Firewall&lt;/a&gt; is an exciting cloud native network security service and provides a lot of features to make our life easier while thwarting security threats out there. While setting up the firewall policies, one thing we are fairly confident of is that Azure Firewall denies all traffic by default. And this has been documented under &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/firewall/rule-processing&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Configure Azure Firewall rules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But things get a bit weird, let’s see how.&lt;/p&gt;
&lt;h1&gt;Anomaly?&lt;/h1&gt;
&lt;p&gt;With the assumption that Azure firewall is denying all requests by default, let’s setup the following policy&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
    &quot;name&quot;: &quot;Allow my vm to another vm&quot;,
    &quot;ipProtocols&quot;: [
        &quot;TCP&quot;
    ],
    &quot;destinationPorts&quot;: [
        &quot;6000&quot;
    ],
    &quot;sourceAddresses&quot;: [
        &quot;10.250.26.36&quot;
    ],
    &quot;sourceIpGroups&quot;: [],
    &quot;ruleType&quot;: &quot;NetworkRule&quot;,
    &quot;destinationIpGroups&quot;: [],
    &quot;destinationAddresses&quot;: [
        &quot;10.200.20.30&quot;
    ],
    &quot;destinationFqdns&quot;: []
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So here we are saying a certain IP is allowed to connect to another certain IP over port 6000. Which is great.&lt;/p&gt;
&lt;p&gt;Now if we run from the vm with IP &lt;code class=&quot;language-text&quot;&gt;10.250.26.36&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Test-NetConnectivity -computer 10.200.20.30 -port 6000&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;we expect and get a success. Great!&lt;/p&gt;
&lt;p&gt;But then if we run&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Test-NetConnectivity -computer 10.200.20.30 -port 443&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;we should expect a failure. This is because port 443 is not allowed. But! But! But! the command succeeds. Added to this astonishment is that no logs appear in the firewall logs which show the port 443 request was allowed.&lt;/p&gt;
&lt;p&gt;What is going on! (◎_◎;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Did the firewall just allow an authorized request?&lt;/li&gt;
&lt;li&gt;What happened to the log?&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;The explanation&lt;/h1&gt;
&lt;p&gt;TCP ping is a unique use case where if there is no allowed rule, the Firewall itself responds to the client’s TCP ping request even though the TCP ping doesn’t reach the target IP address/FQDN. &lt;code class=&quot;language-text&quot;&gt;Test-NetConnection&lt;/code&gt; does a TCP ping. This is &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/firewall/firewall-faq#why-can-a-tcp-ping-and-similar-tools-successfully-connect-to-a-target-fqdn-even-when-no-rule-on-azure-firewall-allows-that-traffic&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;documented&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Regarding logging, records are logged only when a specific rule match occurs. That is, &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/firewall/logs-and-metrics&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Each new connection that matches one of your configured network rules results in a log for the accepted/denied connection.&lt;/a&gt;. I managed to get some clarifications from MS Support and was informed that this specific match is needed in case of TCP ports 80 and 443. For other ports, the denies will get logged even if there is no specific rule match.&lt;/p&gt;
&lt;p&gt;So overall, the firewall is behaving as expected and denying traffic. But it is not logging it as there is no specific rule match.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;One way to get uniform results and gain more confidence will be to have a low priority blanket deny all firewall policy. So, if no allows are matched, a request gets denied and then logged in firewall logs. Although it took me a while to figure this out, was relieved that &lt;strong&gt;no unauthorized requests were being allowed by the firewall&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Hope this was useful and saved you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[ExpressRoute missing spoke route]]></title><description><![CDATA[Introduction After an Azure ExpressRoute is setup we can use Get-AzExpressRouteCircuitRouteTable Cmdlet to view what routes are advertised…]]></description><link>https://rubberduckdev.com/express-route-missing-spoke-route/</link><guid isPermaLink="false">https://rubberduckdev.com/express-route-missing-spoke-route/</guid><pubDate>Sat, 14 Jan 2023 15:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;After an Azure ExpressRoute is setup we can use &lt;a href=&quot;https://learn.microsoft.com/en-us/powershell/module/az.network/get-azexpressroutecircuitroutetable&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Get-AzExpressRouteCircuitRouteTable&lt;/a&gt; Cmdlet to view what routes are advertised on the ExpressRoute. It lists the destinations a data packet can reach if it took that route. And those data packets take the ExpressRoute only when the destination is available on the route table.&lt;/p&gt;
&lt;p&gt;Recently, my team and I came across this issue where a certain spoke IP range wasn’t listed on the route table.&lt;/p&gt;
&lt;h1&gt;ExpressRoute route table in a hub-spoke network topology&lt;/h1&gt;
&lt;p&gt;So while using &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/hybrid-networking/hub-spoke?tabs=cli&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;hub-spoke model&lt;/a&gt; say our setup is as follow:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Hub vnet is 10.10.0.0/22
Spoke1 vnet is 10.20.0.0/22
Spoke2 vnet is 10.30.0.0/22&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Say our on-premises IP ranges are as follows, to which ExpressRoute is the path from our Azure infrastructure:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;162.20.0.0/21&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now traffic from a spoke, say Spoke1 will flow to on-prem and back only if the ExpressRoute route table has entries for &lt;code class=&quot;language-text&quot;&gt;10.20.0.0/22&lt;/code&gt;and &lt;code class=&quot;language-text&quot;&gt;162.20.0.0/21&lt;/code&gt; respectively. These routes are learnt by the ExpressRoute automatically once we deploy the &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/virtual-network/virtual-network-peering-overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;vnet peerings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But when we run the &lt;code class=&quot;language-text&quot;&gt;Get-AzExpressRouteCircuitRouteTable&lt;/code&gt; Cmdlet we see&lt;/p&gt;
&lt;h1&gt;The issue&lt;/h1&gt;
&lt;p&gt;After setting up the infrastructure as described above, traffic was not flowing from on-premises to a resource on Spoke1. The issue was understandable as the ExpressRoute circuit table wasn’t showing entries for the spokes.&lt;/p&gt;
&lt;p&gt;i.e.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Get-AzExpressRouteCircuitRouteTable -DevicePath Primary -ExpressRouteCircuitName my-express-route -PeeringType AzurePrivatePeering -ResourceGroupName my-rg

Network : 162.20.0.0/21
NextHop : 10.300.120.250&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So in the output above, we see that ExpressRoute knows that to get to the on-prem resources, it can use a next hop (which in this case is the ExpressRoute private peering). But it doesn’t know how to get to Azure resources from on-prem. This could manifest in multiple ways while testing, such as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Invoke-WebRequest : The underlying connection was closed: The connection was closed unexpectedly.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Invoke-WebRequest : Unable to connect to the remote server&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we understand the reason, let’s see what is the solution.&lt;/p&gt;
&lt;h1&gt;Solution - check vnet peering&lt;/h1&gt;
&lt;p&gt;The hub to spoke vnet peering should look like this:&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 450px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/974e440c53bbeac7291989686d709643/7f757/hub-spoke-peering.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.50511945392492%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAABJ0AAASdAHeZh94AAABv0lEQVQoz22SCY6jMBBFOcSE1bsJYBIw3kPYcv9TtZxkenrUkUqWS3Lp+7/6yTQOc/CXXhij5Th4Z6wxwfvh2lOCQFW+qiqL35UwfehwX5Z5X5djXy+iJRjVnCMAijzN0lOsLP08zJvOWHtsy+zDY1/UNCk5qmm0WhulvDHeO60mWH4QTwgElGBGMEGoZpQSEluKOaOUEk4JZ7SuGfioPI3DzVv5lNJ60koarazRRktntbXWaFVz+m3+J4WEqkP5ed/WfVkej61rGwQBwYhgSDCiBGMMyyJ/mc9fCNJT/qSQ1E3nvH8c27Et+3p3zgbn5uC1UlpN1ijvnNGTs8bZ+I/g7HwLepJlkScIVhhCBAGGAIEKAVBzFhFE8agcW0Y4Y5xR9kLwfPD27L030e1krQ3BX3uhJqmVVHIUXQurMktP6elPnqVlnhV/60lbHcrN23q/3fy+Ldu2Hsc2h7Bv62NbrTUuxkaH4BnBRZb9R7sCEUxz5pzRpubNuRZd27VNLzrRNaJre/Fu26Y+1wyC6t9we67leB2Gqxyl1YoSXBb5dx7jVuL5vv+cfMaTkLY5C9H2QlwvPUZxMR/D+DskX9KbcVu6ChfQAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;hub to spoke vnet peering&quot;
        title=&quot;hub to spoke vnet peering&quot;
        src=&quot;/static/974e440c53bbeac7291989686d709643/7f757/hub-spoke-peering.png&quot;
        srcset=&quot;/static/974e440c53bbeac7291989686d709643/8890b/hub-spoke-peering.png 293w,
/static/974e440c53bbeac7291989686d709643/7f757/hub-spoke-peering.png 450w&quot;
        sizes=&quot;(max-width: 450px) 100vw, 450px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br/&gt;
The issue we found is that the spokes were not using the hub&apos;s gateway. This is a setting we need on the spoke side of vnet peering. And by default, it is set to none.
&lt;br/&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 455px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c83fa68d831903689ca359b336599882/d2e8e/spoke-hub-peering.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.75085324232082%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAABJ0AAASdAHeZh94AAABuElEQVQoz3WQWa7cIBREvYT3EbUB44HJ7R48MFzAdreflSwg38l6svCItiJFSkcqoRKo7qVOdun1/vVbDLBtz8c6r0tw1sQQxqFv6opgVBD8P2W0M50OIToPbtseXSuPAC0wLUhJC1oQSsj7sBSsv9882LHvo/d6GqyerNHOGqsnMAbAOasrSsi/YVaVZyU4Z+IlKbgUXCmpJFdStMmIuirfb+56/fjclyXuz23fn8G7aRyMHo0enDNGa6NHydn7cHUxl8kDGGv0sgQlOMpPxxtGiGBEU//UmRKS+r8QlK+bTPJ6uF+9d84aMJOzZonzc12s1eAcOLvOEcDMMXqAGCB4/1yX2zhgjDLGGiXlUU/K1JazRgmhpJBScFbTghCMDh0/+sBoJfhMi+zStQEcgEtTg1+WGV6cPVjvYbjfJGd1ReuqpAQTjHKMvmP0Q4qCFhltZC2U5A3nzQH8RZi3SiUj+FnJVsnu3CrOTk39M89/1dVHGoWy6uquZl4DzDHu28ODSzvBLXO0Rs9zWGIYp9GBBWt8gJ7V+JWkCRirh/vNWm1tYiM4O52+YJSj/HScf5skjMo/5X8D5KprnEsZfxEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;spoke to hub vnet peering&quot;
        title=&quot;spoke to hub vnet peering&quot;
        src=&quot;/static/c83fa68d831903689ca359b336599882/d2e8e/spoke-hub-peering.png&quot;
        srcset=&quot;/static/c83fa68d831903689ca359b336599882/8890b/spoke-hub-peering.png 293w,
/static/c83fa68d831903689ca359b336599882/d2e8e/spoke-hub-peering.png 455w&quot;
        sizes=&quot;(max-width: 455px) 100vw, 455px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Once we have got the spokes to use the hub’s gateway, the resulting route table for ExpressRoute should show a &lt;code class=&quot;language-text&quot;&gt;NextHop&lt;/code&gt; for Spoke vnet IP ranges as below. The NextHop being the hub vnet gateway IP.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Get-AzExpressRouteCircuitRouteTable -DevicePath Primary -ExpressRouteCircuitName my-express-route -PeeringType AzurePrivatePeering -ResourceGroupName my-rg

Network : 162.20.0.0/21
NextHop : 10.300.120.250

Network : 10.20.0.0/22
NextHop : 10.10.0.10

Network : 10.30.0.0/22
NextHop : 10.10.0.10&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So the solution is to set &lt;code class=&quot;language-text&quot;&gt;Use the remote virtual network&apos;s gateway or Route Server&lt;/code&gt; using your favourite IaC.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saved you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Azure DevOps service connection with Service Principal using Certificate]]></title><description><![CDATA[Banner from wixstatic Introduction Azure DevOps service connection to Azure, using a Service Principal can use a secret (a password) or a…]]></description><link>https://rubberduckdev.com/azure-devops-service-connection-using-certificate/</link><guid isPermaLink="false">https://rubberduckdev.com/azure-devops-service-connection-using-certificate/</guid><pubDate>Fri, 23 Sep 2022 16:30:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Banner from &lt;a href=&quot;https://static.wixstatic.com/media/f88c3e_95320527a1014a50afe209e77b955882~mv2.png/v1/fit/w_890%2Ch_488%2Cal_c/file.png&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;wixstatic&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Azure DevOps service connection to Azure, using a Service Principal can use a secret (a password) or a certificate. In this post, I am sharing how I recently used a certificate to achieve authentication for a service connection. As described in this &lt;a href=&quot;https://learn.microsoft.com/en-us/answers/questions/296997/creating-and-using-certificates-for-azure-sevice-p.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;MS Community post&lt;/a&gt;, we can use a certificate (AsymmetricX509Cert) with any type of issuers like Self-Sign, Public or Internal CA. Self-signed is the easiest approach, in my opinion, it mimics the same logic of us setting a password and resetting it when it expires.&lt;/p&gt;
&lt;h1&gt;Generating a self-signed certificate&lt;/h1&gt;
&lt;p&gt;There is a difference between the certificates we upload for Azure Service Principal and the Azure DevOps service connection.&lt;/p&gt;
&lt;h2&gt;Azure Service Principal Certificate&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0eaaa1abcf492775005da56c9cb48419/5d6e7/service-principal-upload-cert.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.177474402730375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABMElEQVQY0yWPa2rkMBCEfYJNWJiMpVbr1VJLLUu2xslCYHfJ/W+VOIGiqPqgftTSxpEooAGLpksdW61MNcfKVDLJ5bFmqkwUbAqWgo0eLWgDehlbLZwRNACEsifZeXskObjNLEfpD26T6uG5g2fwBTwrm1+01VotrfecIhq45vQHwqn9Y3UT4gnhvEG/mfFbtV+38nyvTy/1+S5P93ZTxYJe3v++f/z/15r0RI/eh9RZeS95SplSthwleol+S7GlwA6DXiOohDobWPY5X+dhLSYDpdbS2m7gRHwjmhadAfTOx2DQ+OCVVj4GQANakYFFRg/OrutKWk2pU6QlEoqHyCjcErVEnXOyOJgJ4dzaKMzOM+JyvJ2bVOcsauVBXzLgjXZXhWDgB/6EYOCbX+Hr8yeizjkcMrkZRwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;az-sp&quot;
        title=&quot;az-sp&quot;
        src=&quot;/static/0eaaa1abcf492775005da56c9cb48419/463a7/service-principal-upload-cert.png&quot;
        srcset=&quot;/static/0eaaa1abcf492775005da56c9cb48419/8890b/service-principal-upload-cert.png 293w,
/static/0eaaa1abcf492775005da56c9cb48419/1f316/service-principal-upload-cert.png 585w,
/static/0eaaa1abcf492775005da56c9cb48419/463a7/service-principal-upload-cert.png 1170w,
/static/0eaaa1abcf492775005da56c9cb48419/5d6e7/service-principal-upload-cert.png 1350w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We need to upload a certificate (public key) with one of the following file types: .cer, .pem, .crt. And it has to be the public key only.&lt;/p&gt;
&lt;h2&gt;Azure DevOps Service Connection Certificate&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 450px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8f5e02baf25c2ded598f57e2b3fc756d/7f757/ados-cert.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 123.20819112627986%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAIAAAC+dZmEAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACpElEQVQ4y5VU7Y6bOhDl/d+sv5pko5S92WzWQIwDGIw9Hn8CV+BttK3aavfo6GhAHA+eGU3WNE1RFE3TMMaapun7nlLKGKuqquu6eZ6XPyHGGELICCHH4/F0OuV5/vLycrlcDodDnudPT0+XywURvffuA6ZpWpbFOWetzdIZzrkQQvrOGBM2pNgYg4haa2OM1jrG+MifGT+hdekkREyavkbEv/32u9n7YK313oewBkm998uy/Nu5mvu+f319JYTUdV1V1e12KzdYa92f8HgbY8yUUsMGzrn+AKWUlDLpA0pKUKsKIaSS2SBEUVa3rT3tBzRN84i7rmvbjnNOm+GVdlUzKAXOuUwa30or0aWqpmwAIIRIaY0x8zzHGKdp8nE2fnLxvRqZcw4AtNYAkOr0eWRa675/v7O19mtmRFxvL+U4jsMwAMAXzEqpruuGYej7vm3bcRzNb9gmJwT/gSGN42p+20ApLYqiLEtCSLHher0SQs4vl1vNNBoFqDSCRr1yRQYAnHMhROq22KCUSq3WAL0yjTSdNFyZVuIAdvPrNXOM0XsfY0zjnabns3c2xtR1fb/fEXH5IjLrnAQYlUJrXQi41sZVMpDRh+AAjQvhr+YA4IQQjPWUdlV1J4W4lfmF/Vf0I63q69tQ1zPipPUvBJiszfr9vsnz5nQyZRkZi6wOdR3vNDK6Boz5urZV9QvL0lGqz+esvV7vQlDGwFo/z26a3DTZGO0WJPrfGGNYFqA0U2U5LUv0fpnnz3JbYyMhmXh7i/Ps0zLxPq2U93hrm9/orH2otzZM02rmhHAALkQvZbepAODjyIUYtR61VogScQ2MkdvjuO1Bw1jGv30bjsfhcOC7Xbfb9fs93+9X3e3Ujx/4/Kyfn3Gj/qk6z835LL5//x9YtJidKIMTzQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ados-cert&quot;
        title=&quot;ados-cert&quot;
        src=&quot;/static/8f5e02baf25c2ded598f57e2b3fc756d/7f757/ados-cert.png&quot;
        srcset=&quot;/static/8f5e02baf25c2ded598f57e2b3fc756d/8890b/ados-cert.png 293w,
/static/8f5e02baf25c2ded598f57e2b3fc756d/7f757/ados-cert.png 450w&quot;
        sizes=&quot;(max-width: 450px) 100vw, 450px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In Azure DevOps service connection we need to provide PEM file content. Include both certificate and private key content. This is different to what we uploaded onto the Azure service principal.&lt;/p&gt;
&lt;h2&gt;Generation script&lt;/h2&gt;
&lt;h3&gt;Pre-requisite&lt;/h3&gt;
&lt;p&gt;We need &lt;code class=&quot;language-text&quot;&gt;OpenSSL&lt;/code&gt; installed. It can be installed using &lt;a href=&quot;https://community.chocolatey.org/packages/openssl&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Chocolatey&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;The script&lt;/h3&gt;
&lt;p&gt;So I followed a few blogs such as &lt;a href=&quot;https://arsenvlad.medium.com/certificate-based-auth-with-azure-service-principals-from-linux-command-line-a440c4599cae&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Certificate-based auth with Azure Service Principals from Linux command line&lt;/a&gt; and &lt;a href=&quot;https://securecloud.blog/2021/04/13/azure-devops-use-certificate-for-azure-service-connection-spn/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure DevOps – use certificate for Azure Service Connection SPN&lt;/a&gt; and put together the following PowerShell script which generates the certificates in the correct format and also shows a helpful message on where to use which one:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/realrubberduckdev/14b38960a66bb33365af3aaadafcebc9#file-generateselfsignedcertificate-ps1&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Github gist&lt;/a&gt; available.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;param (
    [string]$CertificateNamePrefix,
    [int]$ExpiryInDays
)

$unsecurePassword = Read-Host &quot;Enter password for certificate&quot; -AsSecureString

$certFileName = &quot;$CertificateNamePrefix-cert.pem&quot;
$certPrivateKeyFileName = &quot;$CertificateNamePrefix-key.pem&quot;
$certPackFileName = &quot;$CertificateNamePrefix-pack.pfx&quot;
$certPemWithBagAttributesFileName = &quot;$CertificateNamePrefix-PemWithBagAttributes.pem&quot;

# generate cert.pem to be uploaded for spn
openssl req -x509 -days $ExpiryInDays -newkey rsa:2048 -keyout $certPrivateKeyFileName -out $certFileName
Read-Host “Check that $certFileName and $certPrivateKeyFileName are generated. Then press ENTER to continue...”

# generate pack to put private key and cert (public key) together
openssl pkcs12 -inkey $certPrivateKeyFileName -in $certFileName -export -out $certPackFileName -passout &quot;pass:$unsecurePassword&quot;
Read-Host “Check that $certPackFileName is generated. Then press ENTER to continue...”

# generate merged pem for pasting into Azure DevOps servce connection configuration
openssl pkcs12 -in $certPackFileName -passin &quot;pass:$unsecurePassword&quot;  -out $certPemWithBagAttributesFileName -nodes

Write-Host &quot;Upload $certFileName to Azure Service principal.&quot;
Write-Host &quot;Copy and paste content of $certPemWithBagAttributesFileName in Azure DevOps service connection certificate textbox.&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For example, a test run can look like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;PS C:\&gt; .\GenerateSelfSignedCertificate.ps1 -CertificateNamePrefix TestAdos -ExpiryInDays 730
Enter password for certificate: ******
Generating a RSA private key
.............+++++
.......................................................................+++++
writing new private key to &apos;TestAdos-key.pem&apos;
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter &apos;.&apos;, the field will be left blank.
-----
Country Name (2 letter code) [AU]:UK
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:.
Email Address []:.
Check that TestAdos-cert.pem and TestAdos-key.pem are generated. Then press ENTER to continue...:

Enter pass phrase for TestAdos-key.pem:
Check that TestAdos-pack.pfx is generated. Then press ENTER to continue...:

Upload TestAdos-cert.pem to Azure Service principal.
Copy and paste content of TestAdos-PemWithBagAttributes.pem in Azure DevOps service connection certificate textbox.
PS C:\dev\setup-dev-evn\AzureAutomation\self-signed-cert-generation&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This script is useful as it helps create both the different certificates which we need to use as well as provides a helpful message saying which one goes where. Hope this was useful and saved you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[VNet peering using Azure Policy]]></title><description><![CDATA[Introduction Azure policy is a service on Azure which helps us achieve organisation-wide resource governance by creating policies in Azure…]]></description><link>https://rubberduckdev.com/azure-policy-vnet-peering/</link><guid isPermaLink="false">https://rubberduckdev.com/azure-policy-vnet-peering/</guid><pubDate>Fri, 19 Aug 2022 10:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Azure policy is a service on Azure which helps us achieve organisation-wide resource governance by creating policies in Azure to govern every existing or future resource deployed. I was investigating if we can use this to reduce the workload on our infrastructure deployment and automate connectivity.&lt;/p&gt;
&lt;p&gt;The overall idea is that use a policy definition to create a virtual network peering between hub and spokes.&lt;/p&gt;
&lt;h1&gt;Hub-spoke network topology&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/hybrid-networking/hub-spoke?tabs=cli&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;hub-spoke model&lt;/a&gt; is a general network topology used, mainly if we want a hybrid setup. There will be a hub virtual network acting as the central point of connectivity to the on-premises network. Spoke virtual networks are used to isolate workloads in their virtual networks. Two virtual networks can be connected using something called virtual network peering.&lt;/p&gt;
&lt;p&gt;So in this post, we can assume that the hub virtual network already exists. And we have multiple development environment virtual networks being deployed on demand. The hub can be RBAC restrictions regarding if those deployment users and processes have access. This is where azure policy steps in.&lt;/p&gt;
&lt;h1&gt;Azure policy&lt;/h1&gt;
&lt;p&gt;I had to refresh my azure policy memory and the following video helped a lot.&lt;/p&gt;
&lt;p&gt;
          &lt;div
            class=&quot;gatsby-resp-iframe-wrapper&quot;
            style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden;margin-bottom: 1rem&quot;
          &gt;
            &lt;div class=&quot;embedVideo-container&quot;&gt;
            &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/eLYfeKLcwec?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0;
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
          &quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
        &lt;/div&gt;
          &lt;/div&gt;
          &lt;/p&gt;
&lt;h1&gt;Policy definition&lt;/h1&gt;
&lt;p&gt;After a few trials and error, this is the policy definition that finally worked&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
    &quot;mode&quot;: &quot;Indexed&quot;,
    &quot;policyRule&quot;: {
        &quot;if&quot;: {
            &quot;field&quot;: &quot;type&quot;,
            &quot;equals&quot;: &quot;Microsoft.Network/virtualNetworks&quot;
        },
        &quot;then&quot;: {
            &quot;effect&quot;: &quot;deployIfNotExists&quot;,
            &quot;details&quot;: {
                &quot;type&quot;: &quot;Microsoft.Network/virtualNetworks/virtualNetworkPeerings&quot;,
                &quot;resourceGroupName&quot;: &quot;testrg&quot;,
                &quot;roleDefinitionIds&quot;: [
                    &quot;/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7&quot;
                ],
                &quot;deployment&quot;: {
                    &quot;properties&quot;: {
                        &quot;mode&quot;: &quot;incremental&quot;,
                        &quot;template&quot;: {
                            &quot;$schema&quot;: &quot;https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json&quot;,
                            &quot;contentVersion&quot;: &quot;1.0.0.0&quot;,
                            &quot;parameters&quot;: {
                                &quot;newvnetId&quot;: {
                                    &quot;type&quot;: &quot;string&quot;
                                },
                                &quot;newvnetName&quot;: {
                                    &quot;type&quot;: &quot;string&quot;
                                },
                                &quot;hubvnetId&quot;: {
                                    &quot;type&quot;: &quot;string&quot;
                                },
                                &quot;hubvnetName&quot;: {
                                    &quot;type&quot;: &quot;string&quot;
                                }
                            },
                            &quot;resources&quot;: [
                                {
                                    &quot;type&quot;: &quot;Microsoft.Network/virtualNetworks/virtualNetworkPeerings&quot;,
                                    &quot;apiVersion&quot;: &quot;2020-11-01&quot;,
                                    &quot;name&quot;: &quot;[concat(parameters(&apos;hubvnetName&apos;),&apos;/&apos;, concat(parameters(&apos;hubvnetName&apos;),&apos;-&apos;, parameters(&apos;newvnetName&apos;)))]&quot;,
                                    &quot;properties&quot;: {
                                        &quot;peeringState&quot;: &quot;Connected&quot;,
                                        &quot;remoteVirtualNetwork&quot;: {
                                            &quot;id&quot;: &quot;[parameters(&apos;newvnetId&apos;)]&quot;
                                        },
                                        &quot;allowVirtualNetworkAccess&quot;: true,
                                        &quot;allowForwardedTraffic&quot;: true,
                                        &quot;allowGatewayTransit&quot;: false,
                                        &quot;useRemoteGateways&quot;: false
                                    }
                                },
                                {
                                    &quot;type&quot;: &quot;Microsoft.Network/virtualNetworks/virtualNetworkPeerings&quot;,
                                    &quot;apiVersion&quot;: &quot;2020-11-01&quot;,
                                    &quot;name&quot;: &quot;[concat(parameters(&apos;newvnetName&apos;),&apos;/&apos;, concat(parameters(&apos;newvnetName&apos;),&apos;-hub&apos;))]&quot;,
                                    &quot;properties&quot;: {
                                        &quot;peeringState&quot;: &quot;Connected&quot;,
                                        &quot;remoteVirtualNetwork&quot;: {
                                            &quot;id&quot;: &quot;[parameters(&apos;hubvnetId&apos;)]&quot;
                                        },
                                        &quot;allowVirtualNetworkAccess&quot;: true,
                                        &quot;allowForwardedTraffic&quot;: true,
                                        &quot;allowGatewayTransit&quot;: false,
                                        &quot;useRemoteGateways&quot;: false
                                    }
                                }
                            ]
                        },
                        &quot;parameters&quot;: {
                            &quot;newvnetId&quot;: {
                                &quot;value&quot;: &quot;[field(&apos;id&apos;)]&quot;
                            },
                            &quot;newvnetName&quot;: {
                                &quot;value&quot;: &quot;[field(&apos;name&apos;)]&quot;
                            },
                            &quot;hubvnetName&quot;: {
                                &quot;value&quot;: &quot;hub-vnet&quot;
                            },
                            &quot;hubvnetId&quot;: {
                                &quot;value&quot;: &quot;/subscriptions/bf97e223-050a-44b3-b142-c8f916b707f2/resourceGroups/testrg/providers/Microsoft.Network/virtualNetworks/hub-vnet&quot;
                            }
                        }
                    }
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The definition above creates a virtual network peering with the hub virtual network for every new virtual network deployed.
The parameters section (which I am sure can be optimized) automatically gets the name and id of the new virtual network being deployed and the hub details are hard coded.&lt;/p&gt;
&lt;h2&gt;Testing Tips&lt;/h2&gt;
&lt;h3&gt;Patience&lt;/h3&gt;
&lt;p&gt;Azure policies take time to get applied. It is difficult to predict when it has got applied, so I normally had to wait 10-15mins before checking if it has worked. Officially Azure portal will say it can take up to 30mins and yes it can.&lt;/p&gt;
&lt;h3&gt;ARM&lt;/h3&gt;
&lt;p&gt;Test the &lt;code class=&quot;language-text&quot;&gt;deployment&lt;/code&gt; section by deploying outside of the policy definition. Some of the errors during deployment can take 5-10mins or longer to show up.&lt;/p&gt;
&lt;h3&gt;Activity log&lt;/h3&gt;
&lt;p&gt;Check the activity log of the resource group to spot &lt;code class=&quot;language-text&quot;&gt;deployIfNotExists&lt;/code&gt; and if it has succeeded. It normally should give a useful error message if failed.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saved you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Finding the outbound IP address of an azure function/webapp]]></title><description><![CDATA[Introduction It is quite useful to know the outbound IP address of an Azure function app or an app service. Both these services are the same…]]></description><link>https://rubberduckdev.com/app-outbound-ip/</link><guid isPermaLink="false">https://rubberduckdev.com/app-outbound-ip/</guid><pubDate>Mon, 01 Aug 2022 10:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;It is quite useful to know the outbound IP address of an Azure function app or an app service. Both these services are the same under the hood, hence the following techniques apply to both.&lt;/p&gt;
&lt;h1&gt;Finding the outbound IP address&lt;/h1&gt;
&lt;h2&gt;Check under properties for all possible ones&lt;/h2&gt;
&lt;p&gt;Under the app (function app or app service) properties we can see the list of outbound IP address that Azure can possibly use.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/189d3e98af82f2edd3d8ea13270b68e8/eb1f9/properties.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.82935153583618%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAA7CAAAOwgEVKEqAAAABTUlEQVQoz42S21KDMBRF8w2O2iRAQgjhDhFapK229P9/amtibz5IfVhz9jDMOvvMhORtj8b2MMZAJwqcMwQORm+ZMzBGPS7z87zHfYvCAKTseqg0R2t7tE0FunpBwOkvEWUUijPoBdllkqos8Pr8hP12wnozYsVDhFF0beREdcDRhAHkH8J7SGc7xFJgsx4wjiMY56CMQTGKmjNUnHkRPS/gi6xA6rrxtzvhNL0jjgIMUsDGEloKhCJCFIX+n0e4y0jfv0GKyAt32wlVkaMtC6TGIMsM8szApBpSCH/JEkJnII21EGHgZfvdFqlOkOcZcpP+yEyK7JwdlyU+m1t2SCfsrPUNnHA+HlFXpZe4J6R14hf8lzirQd5s68+cjwccPj/8psI1dGQGKpaIY+nnI0RagMzDgEonOJ3ma8Ouba64Z5WoGIlSyyQK8rvhF2lvF4OJxdFSAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Properties tab&quot;
        title=&quot;Properties tab&quot;
        src=&quot;/static/189d3e98af82f2edd3d8ea13270b68e8/463a7/properties.png&quot;
        srcset=&quot;/static/189d3e98af82f2edd3d8ea13270b68e8/8890b/properties.png 293w,
/static/189d3e98af82f2edd3d8ea13270b68e8/1f316/properties.png 585w,
/static/189d3e98af82f2edd3d8ea13270b68e8/463a7/properties.png 1170w,
/static/189d3e98af82f2edd3d8ea13270b68e8/eb1f9/properties.png 1661w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This tab shows a whole list of IP addresses and not the specific IP actually used for an outbound request. Also if app is vnet integrated and the subnet has a route table, these IPs won’t be used. So the best source of truth will be doing an outbound request from the app’s console. Let’s see how next.&lt;/p&gt;
&lt;h2&gt;Use console to find IP for specific request&lt;/h2&gt;
&lt;p&gt;This trick uses as free service called &lt;code class=&quot;language-text&quot;&gt;https://www.ipify.org/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1018px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/872d480510fb043eca50a179cfb63bc1/99347/console.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.50511945392492%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABdklEQVQoz5WP3U7CQBBGp6VAba2CpdDdbe0P3bbbst0aRIwmEr3yykQlKCpCIu//DMZqohdi5ORkMjdfZj7w+agcjvnwNCtHqRimxdCjudNPHL//YRgTjAlGvwrp2cXxkeA5K3IWhT7tBwR1sf1DZG8SMpaWovAOXYzs8/Mz0zQB5FpzV95py+qe3FDlzUDK0qNSlKIIA3+9XjuOAwCSokGtWamCJMEmcs5YmsQ0ch3yuniJ/KipaIbe1vWDXa1l6G1Db9cbOtQ1qDWgvgMNHRQVlObHTLIsCgNk9yzLfFssJseCRW7mBaGNQhtT4lLi9Ix9kOTKGsiVnzvnXBQD3zvsWp3l6yKOwj1DO9hvqYoiAUgAcuXvcF6IQR74ntUxV6slJlVnSfqr6nfnQuQ5iylFdm86vccYf4X/w2Q8vjw9+Tw+nz8ihLYIX0+ubiYXo1JQGj0/PW0XTmhcDPIkiV2H3N3dbvc2y1jgewSjrtV5mM22Cr8DrK03OYmL2awAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Console tab&quot;
        title=&quot;Console tab&quot;
        src=&quot;/static/872d480510fb043eca50a179cfb63bc1/99347/console.png&quot;
        srcset=&quot;/static/872d480510fb043eca50a179cfb63bc1/8890b/console.png 293w,
/static/872d480510fb043eca50a179cfb63bc1/1f316/console.png 585w,
/static/872d480510fb043eca50a179cfb63bc1/99347/console.png 1018w&quot;
        sizes=&quot;(max-width: 1018px) 100vw, 1018px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We go into the app’s console and type the following command:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;C:\home\site\wwwroot&gt;curl -s https://api.ipify.org&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That should return the outbound IP from which the request went to &lt;code class=&quot;language-text&quot;&gt;ipify&lt;/code&gt; service.&lt;/p&gt;
&lt;h3&gt;Mimic ipipfy using own service&lt;/h3&gt;
&lt;p&gt;We can also create our own service e.g. an Azure function.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 941px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3fe5f9aeaa05d51c8111b5796cde097a/4de97/own-service.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.85324232081911%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABiElEQVQoz22R3ZLiIBCF8w6OCnTT4R+SEKLRxHV2dHXe/6G2Ep292q8oOFx0w+lTUTO2ZTyMUy5D7HqX2pgHZQOSQhMEAHAmOAPB17Xof9dKH/9crp+fv6bTeMxtE70L3pKE/W7L2Z4kAgCRBBASARFrKUmi4JwzVikpUgxEUnAmJey2WwAQAEppUtpYi1RrYwTgsmOttVJKfXx8bDabKsbY9xlfjUEgiBhTCiG3TZuiVsoYwzmHld12u1vZr1TX+/P7+3kaj6XPh6GkGKmuvQspeG8t/VD/j6qP9Xw+aVW/Hq9JKmO789fj8bjdbtM0lVJCCESEiPLFKoioKonm89Ea/ZqhRFDG2JR9TD42TdPGuPiwzvnlK1Yb41axFHPGONu/k+CMJFrnQmqaru/6knMecjfkrm+bQ+mPQyk5Lw5zVkpVKDi+M+SCs5rIGu1iO8xfj+fjfr9d5un3fL7O5+tlHg9D26QUQ0pJa139pP8uJonOe+OsXIIy1lpj7HI4r5SW8mX7PcW/BMdRnZmim3MAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Console tab&quot;
        title=&quot;Console tab&quot;
        src=&quot;/static/3fe5f9aeaa05d51c8111b5796cde097a/4de97/own-service.png&quot;
        srcset=&quot;/static/3fe5f9aeaa05d51c8111b5796cde097a/8890b/own-service.png 293w,
/static/3fe5f9aeaa05d51c8111b5796cde097a/1f316/own-service.png 585w,
/static/3fe5f9aeaa05d51c8111b5796cde097a/4de97/own-service.png 941w&quot;
        sizes=&quot;(max-width: 941px) 100vw, 941px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;The dotnet code below can return the caller’s IP&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;#r &quot;Newtonsoft.Json&quot;

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task&amp;lt;IActionResult&gt; Run(HttpRequest req, ILogger log)
{
    string responseMessage = $&quot;Caller ip = {req.HttpContext.Connection.RemoteIpAddress.ToString()}&quot;;
    return new OkObjectResult(responseMessage);
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can call the service using postman to test or from console of another app&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 697px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5b9cc2b09af1ca8873c5c4563d877e41/62d75/call-using-postman.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 80.54607508532423%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAIAAACZeshMAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABUUlEQVQoz5WSDY6DIBCFucgWUIZREBQhCmq3vf+pNmhrTbrtdr8QAiTvzQ9DdKOnlOIYY4rDMMY4DsM4pWmeF+/96evrdKKUUvYbxBjTWquUahrjfVhXJoTQ9865zjnXWlvhg+oO8SE43wsJ1pgpTeMY52keU5znKcW4LMv5+5zSWKt6DdAopR7i0NpLCtX6AgCcc6yQCl6WZVEUQgiUEkAIyMgM7FUQIUrbGKMbyhjnvCiKvPO8P4OIQmTTDdJa23bdJvsTpfSW3U08DEPf95TS4jOOYQillHN+TOYNAHC8ks3sQ9jalx3yD+kThDHOVku2nZ7s34l9a1FKrPIIyRq5LFWtJMqb3VsvMsd4vV6MNVpr21qllTHGOYeITWMqRFXhKzmp6trlIe42sXPOWouIALDNHIJ4GZnnUpmUUIpSbFMoYE94T/6F+P4HRz5s2A+zsIvKwokwZAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Console tab&quot;
        title=&quot;Console tab&quot;
        src=&quot;/static/5b9cc2b09af1ca8873c5c4563d877e41/62d75/call-using-postman.png&quot;
        srcset=&quot;/static/5b9cc2b09af1ca8873c5c4563d877e41/8890b/call-using-postman.png 293w,
/static/5b9cc2b09af1ca8873c5c4563d877e41/1f316/call-using-postman.png 585w,
/static/5b9cc2b09af1ca8873c5c4563d877e41/62d75/call-using-postman.png 697w&quot;
        sizes=&quot;(max-width: 697px) 100vw, 697px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saved you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Terraform - working around 'error installing provider']]></title><description><![CDATA[Introduction Recently many teams I have been working with have been hitting this  issue with Terraform. We have noticed that it is only…]]></description><link>https://rubberduckdev.com/tf-installing-provider-error/</link><guid isPermaLink="false">https://rubberduckdev.com/tf-installing-provider-error/</guid><pubDate>Tue, 19 Jul 2022 13:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Recently many teams I have been working with have been hitting this &lt;code class=&quot;language-text&quot;&gt;error installing provider&lt;/code&gt; issue with Terraform. We have noticed that it is only happening with Terraform 0.11 (could be happening with other versions, we just haven’t noticed it yet).&lt;/p&gt;
&lt;p&gt;The error message is in lines of&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Error installing provider &quot;null&quot;: openpgp: signature made by unknown entity. 
Terraform analyses the configuration and state and automatically downloads 
plugins for the providers used. However, when attempting to download this 
plugin an unexpected error occured. 
This may be caused if for some reason Terraform is unable to reach the 
plugin repository. The repository may be unreachable if access is blocked 
by a firewall. 
If automatic installation is not possible or desirable in your environment, 
you may alternatively manually install plugins by downloading a suitable 
distribution package and placing the plugin&apos;s executable file in the 
following directory: 
    terraform.d/plugins/windows_amd64 &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Workaround&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: This is not recommended for production use.&lt;/p&gt;
&lt;p&gt;The only way it seems to work for us is to use unverified plugins.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;terraform init -verify-plugins=false&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Explanation&lt;/h1&gt;
&lt;p&gt;The issue here is the &lt;code class=&quot;language-text&quot;&gt;openpgp&lt;/code&gt; plugin signature has changed and by default &lt;code class=&quot;language-text&quot;&gt;Terraform&lt;/code&gt; performs verification for any plugins used. By skipping verification, we are at risk of getting security vulnerabilities into our deployment process. Hence &lt;strong&gt;it is not recommended to skip plugin verification in production systems&lt;/strong&gt;. This can be a quick workaround while you possibly upgrade your Terraform and provider versions to use the latest valid plugins.&lt;/p&gt;
&lt;p&gt;Please note that I was unable to find any official documentation about this &lt;code class=&quot;language-text&quot;&gt;terraform init&lt;/code&gt; parameter. The only place it is described is at &lt;a href=&quot;https://www.lightnetics.com/topic/2956/terraform-init-help&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;lightnetics.com&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saved you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using Azure App Service managed certificate]]></title><description><![CDATA[Introduction Took me a while on how to enforce https on the websites I have deployed using Azure App Service. The main blocker for me was…]]></description><link>https://rubberduckdev.com/custom-domain-ssl-binding/</link><guid isPermaLink="false">https://rubberduckdev.com/custom-domain-ssl-binding/</guid><pubDate>Fri, 28 Jan 2022 16:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Took me a while on how to enforce https on the websites I have deployed using Azure App Service. The main blocker for me was getting a &lt;a href=&quot;https://www.digicert.com/how-tls-ssl-certificates-work&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;SSL/TLS certificate&lt;/a&gt;. Given I have mostly hobby websites, one of the options was to get a free one from &lt;a href=&quot;https://letsencrypt.org&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;https://letsencrypt.org&lt;/a&gt;. But it quickly became clear from their documentation that there is a learning curve! Then I found the easy way out, &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate?tabs=apex&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure App Service free managed certificate&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Azure App Service managed certificate&lt;/h1&gt;
&lt;p&gt;The Microsoft documentation itself is already very good, I will try to add the quick steps I did. Once we got a custom domain validated, then on it is quite simple.&lt;/p&gt;
&lt;p&gt;Overall there are two main steps, create the certificate and then bind your custom domain.&lt;/p&gt;
&lt;h2&gt;Create App Service Managed Certificate&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1090px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c7368a515b1a8f0ef951f932a8d4b334/4b918/create-free-cert.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.07508532423208%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAB2HAAAdhwGP5fFlAAABkklEQVQoz32R646bMBCFef9Hq6poI7VK0oRLWHIBbGMDwQYHvgpne/mx7UhHM2NpzviciRptEFIyDAPjOPIYBtRjRAqFPGeoPOcaZ5TXEtFbKjPQ2RE3+U8R9StB02CtZQ3rRh6jR5meMj9zyTKq/My4ksxg/cI088+IpDZUdY0xBpblRTgMtH2PVAqhFEpr/NPjp5Gnn/DTFNS84BidCzUsRNoYpJI455jnGTtO3O53kjgmTdOQszTleDxyPJ2I45j4I5/ihCS/8F4UlGXJ8/kkGm2P0Q1N0/B4PMJvat1yq2qUkjRaUykdBqSUKN1yuVeIoKrldr5QV3WYn6aJyPQGoSR+fhlTS4XWDUYJhBBY58L7Ksl7z7wsdH0fFAXPuw4lJV3XsSwLUXEtec8y1O2Kdw4hajY/Cr4cChopwuZfdqwDc1i8/K6Xvw6y9pG5FIgkps3P4CzaNCRly+Fq6LuWwdoPw//EKm3Fp1f+tvvO182Gt+2W3X7P5m3L8bAjPR1Cv9vt2O/3JEkSjvQ/ZFnGT1TvrRiE/QmEAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Create App Service Managed Certificate&quot;
        title=&quot;Create App Service Managed Certificate&quot;
        src=&quot;/static/c7368a515b1a8f0ef951f932a8d4b334/4b918/create-free-cert.png&quot;
        srcset=&quot;/static/c7368a515b1a8f0ef951f932a8d4b334/8890b/create-free-cert.png 293w,
/static/c7368a515b1a8f0ef951f932a8d4b334/1f316/create-free-cert.png 585w,
/static/c7368a515b1a8f0ef951f932a8d4b334/4b918/create-free-cert.png 1090w&quot;
        sizes=&quot;(max-width: 1090px) 100vw, 1090px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/p&gt;
&lt;h2&gt;Add binding to secure custom domain&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 476px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7dc759ef8182118c0ab0d0c03ebbc3c8/d9059/add-binding.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.84641638225256%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABn0lEQVQ4y51Sy26jQBDk/79hf2F/YJPLnnKIFCmHVQ7ObhTbccDDvOgBhoGhVt1enIftPWSkEiCgqrqqi31V4VmtUNZbBGrRNI2AiL6EYugH3G5+oLR3IN/BWgOlFPq+x1dOYYyD0yW8s7DOwVoL7z2cc+KUnxf1RWSe54soKqXxuNF4flEwxmCvaiFh8HNd13LVWotICAEppSPxiUN2M45J1Ie+Q9sYBHrLkF1O03T8YSE5RyaErNqGgNgR7lYG3652WG0OY8cYhSznfDLaRYfON3BpRhsIZd3g6dWji+ms+iVXJ4SUsowX+xbaOGjfYcoZOU/i8LPL/xVTcPgdjxyjfHh9b/H9RmFXE5QNcE2LShOo7Q5EF5weCRsi2EBHwhh7aH1otiwraZZPGhPUvpQpzjl8a3m9Bf28gW68lMNt82JzBOM4CvjQ6hHbX3+gHn5jVArc+xIHg1eJYymMekH1dAtecCZj8O5J+20re9f1PWJZwa9fQesdRiJM8/yBkIWF0JNHQsQ0TmJ9ecmKwzAI+P59anmekf8V9R6c4V9PdfHuNtMKGAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Add binding to secure custom domain&quot;
        title=&quot;Add binding to secure custom domain&quot;
        src=&quot;/static/7dc759ef8182118c0ab0d0c03ebbc3c8/d9059/add-binding.png&quot;
        srcset=&quot;/static/7dc759ef8182118c0ab0d0c03ebbc3c8/8890b/add-binding.png 293w,
/static/7dc759ef8182118c0ab0d0c03ebbc3c8/d9059/add-binding.png 476w&quot;
        sizes=&quot;(max-width: 476px) 100vw, 476px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Although it has been nicely documented, took me a while to figure that these are the two main steps needed. Hence this post, hope it helps someone else get there a bit quicker. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Features of an ideal IaC language]]></title><description><![CDATA[Introduction Infrastructure as Code (IaC) is the management of infrastructure (networks, virtual machines, load balancers, and connection…]]></description><link>https://rubberduckdev.com/ideal-iac-features/</link><guid isPermaLink="false">https://rubberduckdev.com/ideal-iac-features/</guid><pubDate>Mon, 20 Dec 2021 08:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Infrastructure as Code (IaC) is the management of infrastructure (networks, virtual machines, load balancers, and connection topology) in a descriptive model, using source code. These could be cloud infrastructure of on-prem as well. Once codified we can use all the benefits of version control, testing etc.&lt;/p&gt;
&lt;h1&gt;Festive Tech Calendar 2021&lt;/h1&gt;
&lt;p&gt;This demo is designed as my contribution for &lt;a href=&quot;https://festivetechcalendar.com&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Festive Tech Calendar 2021&lt;/a&gt;, definitely check out the website for many other great topics. Happy Christmas.&lt;/p&gt;
&lt;p&gt;This video is part of the festive tech calendar 2020.&lt;/p&gt;
&lt;p&gt;
          &lt;div
            class=&quot;gatsby-resp-iframe-wrapper&quot;
            style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden;margin-bottom: 1rem&quot;
          &gt;
            &lt;div class=&quot;embedVideo-container&quot;&gt;
            &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/EqSNAFEUu-M?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0;
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
          &quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
        &lt;/div&gt;
          &lt;/div&gt;
          &lt;/p&gt;
&lt;h1&gt;Features of an ideal IaC language - my thoughts&lt;/h1&gt;
&lt;h2&gt;Clarity, simplicity, unity&lt;/h2&gt;
&lt;p&gt;The language we use to deploy resources must be simple, easy to understand has a specific convention. So once written and shared, multiple users can comprehend it in the same way.&lt;/p&gt;
&lt;h2&gt;Orthogonality&lt;/h2&gt;
&lt;p&gt;Orthogonality is the property that means “Changing A does not change B”. This is nicely explained with examples &lt;a href=&quot;https://stackoverflow.com/a/1527430/1228479&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;in this comment on stackoverflow&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Testability&lt;/h2&gt;
&lt;p&gt;Once code is written, infrastructure or otherwise, it will add a lot to our confidence if we can test it. This makes it easier to reuse code with minimal concerns. Along with unit tests, static analysis, security testing are important. Mainly if we use IaC at scale and to help deploy real-world secured and compliant resources.&lt;/p&gt;
&lt;h2&gt;Modularity&lt;/h2&gt;
&lt;p&gt;Ideally, we won’t need to rewrite the same thing again and again. So we should be able to write once and reuse it. Hence modules in IaC are quite important.&lt;/p&gt;
&lt;h2&gt;Private registry&lt;/h2&gt;
&lt;p&gt;Registry as is, to share the modules we discuss in the previous point. A public registry is very nice to have, but a private one is a must-have in my opinion. This will help use IaC at scale, say if you have multiple teams developing and sharing modules in your organization.&lt;/p&gt;
&lt;h2&gt;Custom functions&lt;/h2&gt;
&lt;p&gt;The ability to write custom functions such as string manipulation, date manipulation etc helps configure resources easily. An ideal IaC should have support for custom functions so we won’t have to add scripts between resource deployments.&lt;/p&gt;
&lt;h2&gt;Declarative&lt;/h2&gt;
&lt;p&gt;With an IaC we should be able to say what we want and not how. For example, on Azure, we should say deploy a function app, not call a specific azure management endpoint with a specific request body.&lt;/p&gt;
&lt;h2&gt;IDE support&lt;/h2&gt;
&lt;p&gt;Good IDE experience is very important to be able to write IaC quickly and effectively.&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;Although IaC has to be clear, simple and straightforward, there will be some quirks, exceptions and sometimes bugs. These features, quirks and exceptions etc of an IaC need to be documented to aid the users.&lt;/p&gt;
&lt;h2&gt;Technical support&lt;/h2&gt;
&lt;p&gt;Once an issue happens, users need to feel supported. If something with the IaC doesn’t behave as expected or is difficult to comprehend, technical support becomes very important. Nothing worse than feeling lost and not being able to get help.&lt;/p&gt;
&lt;h2&gt;Cost&lt;/h2&gt;
&lt;p&gt;The final point is cost. This is something teams will need to justify to business. If the IaC is going to hurt business, it will become difficult to adopt.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Windows Terminal - Setup Visual Studio prompt in pwsh]]></title><description><![CDATA[Introduction This post is more for my notes and hopefully helps someone out figure out how to set up Visual Studio environment from pwsh in…]]></description><link>https://rubberduckdev.com/pwsh-dev-prompt/</link><guid isPermaLink="false">https://rubberduckdev.com/pwsh-dev-prompt/</guid><pubDate>Sun, 14 Nov 2021 19:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This post is more for my notes and hopefully helps someone out figure out how to set up &lt;a href=&quot;https://visualstudio.microsoft.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Visual Studio&lt;/a&gt; environment from &lt;a href=&quot;https://github.com/PowerShell/PowerShell&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;pwsh&lt;/a&gt; in &lt;a href=&quot;https://github.com/microsoft/terminal&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Windows Terminal&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Steps&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;From windows terminal hit Ctrl+, to open settings pane. I normally use the json file.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 803px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/aaac2f29275eb1ae3520f4c0b98ed727/ac9fd/terminal-settings.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.60068259385666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABo0lEQVQoz42S3Y6bMBBG8QIJzYZsIwjgUAjxjBEJ+QXbgLaVKuWq1b7/81R2tjdpNttzMXdnvvGMrcvlMgxD3/fXqlQ3DIOUUgghpdzv93VdHw6H3W4nhDgej1mW5X+xlFKr1Wq9XhcaXRljURQtFosgCAgh1gOklMfjCQDQAACcc0ppkiRFUYRhOB6PHcdxXddxnNteQoiu6zebDWMMQMsAkCTJbDabGzzPI4S4htvkVrRd19f1jjGGiLqDlmkcx3meZ1kGAEEQPBn+kVshhGiaBpEDgMnXydPpNE1TSmmapmEY2rY9Go1uZTN2V1WVmVcnI+JVfp5MpgbXde8nS6lOpxO88z72tzSNKH2Zz33fv8r3t/36+r0sS9AOXheGiPM4Trwxe/lKbPvRqYSQTSPMg5EDrgFWbK3y4uemyaMFsW1iuC8r1Ukpm6blZZkj1gx+cy6y/DlaLuLYG3uPks+iF23btKI9nX8g/8XLfVkGSTydfFkuU9/3LcsiH6AX1qnurNRbVb1xvtKz45JSQp7IZ1jb7bYyIGKuPwlD0Key/oM/i4BE/ZsgBQIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;terminal-settings&quot;
        title=&quot;terminal-settings&quot;
        src=&quot;/static/aaac2f29275eb1ae3520f4c0b98ed727/ac9fd/terminal-settings.png&quot;
        srcset=&quot;/static/aaac2f29275eb1ae3520f4c0b98ed727/8890b/terminal-settings.png 293w,
/static/aaac2f29275eb1ae3520f4c0b98ed727/1f316/terminal-settings.png 585w,
/static/aaac2f29275eb1ae3520f4c0b98ed727/ac9fd/terminal-settings.png 803w&quot;
        sizes=&quot;(max-width: 803px) 100vw, 803px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Profile:&lt;/strong&gt; Add pwsh profile as you default one
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 918px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3aeca82637054bf9ca86060c9efdef7e/2c248/default-wt-profile.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 86.00682593856655%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAIAAABSJhvpAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABw0lEQVQ4y3VSW24bMQzMQWqJw4dIaV+xHcdAg7bJZ+9/oUKy3TaJQwyI2Y9ZkjN60ODYLDbz1WLVdii+aplkQK2JNdbogFJ6Xw8QEoc4szMXSAEbiImYIAQeGCTn9Ems0BCrrA51tirqrM6X4VLZJvWlr3BHPIme9+fT8vi9xq8Wr1P92fxH89dWX8IfiXLKu17pcz0sIqdlOq7zcYrz7Oc5Ts1PrZyXeG7+XAxEKac88FGccoaRBDiYK/ceDMPgIIe2fpSMozK9+8EDAb4ssc4SLkWhxIoBggLSSc453V17iNe6PWqtEg7OhIzu9iBK6X/l7oarmEhctZqGcpGemeEGgg7INTm+ff6dTL567Y9EY7O6L75a3duVb6aVpUAMhJzpilvODJ83i9bNvLiaMlHfefQ+k3B7JLsPN+fMqhw1MyXkC3aUd5QSpdH7nPs5E+VSopo6kyu7oINpELqQzwn/y3k129ydUhUEw0FV0ASVEYIZCKJvX4lnIovqh+O2xPPW1tlbtdp0mYq7Cqh+Jc6EBWhtenr7/fTydtwfji2aWbdt2FcyTcAu36k+OUQO7lvxpcQkOhEt6NtesABKdM+v9Ac+uor+AxCYkAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;default-wt-profile&quot;
        title=&quot;default-wt-profile&quot;
        src=&quot;/static/3aeca82637054bf9ca86060c9efdef7e/2c248/default-wt-profile.png&quot;
        srcset=&quot;/static/3aeca82637054bf9ca86060c9efdef7e/8890b/default-wt-profile.png 293w,
/static/3aeca82637054bf9ca86060c9efdef7e/1f316/default-wt-profile.png 585w,
/static/3aeca82637054bf9ca86060c9efdef7e/2c248/default-wt-profile.png 918w&quot;
        sizes=&quot;(max-width: 918px) 100vw, 918px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then open &lt;code class=&quot;language-text&quot;&gt;pwsh&lt;/code&gt; and use command&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;code $PROFILE&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will open up the profile startup script&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following to the profile startup script.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$vsInstallPath = &quot;C:\Program Files\Microsoft Visual Studio\2022\Community&quot;
Import-Module &quot;$vsInstallPath/Common7/Tools/Microsoft.VisualStudio.DevShell.dll&quot;
Enter-VsDevShell -VsInstallPath $vsInstallPath -SkipAutomaticLocation -StartInPath &quot;C:\Dev&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; The &lt;code class=&quot;language-text&quot;&gt;StartInPath&lt;/code&gt; parameter is for my local folder where I normally checkout code.
&lt;em&gt;Note:&lt;/em&gt; This is specific to VS 2022. May need changing as per VS installation.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saves you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Deploying DocFX on Azure static web app]]></title><description><![CDATA[Introduction Azure Static Web App The Azure static web app is a modern web app service that helps with streamlined full-stack development…]]></description><link>https://rubberduckdev.com/docfx-azure-static-web-app/</link><guid isPermaLink="false">https://rubberduckdev.com/docfx-azure-static-web-app/</guid><pubDate>Fri, 30 Jul 2021 18:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;h2&gt;Azure Static Web App&lt;/h2&gt;
&lt;p&gt;The Azure static web app is a modern web app service that helps with streamlined full-stack development. The feature I like most about it is the ease of CI-CD managed straight from the resource deployment. Refer documentation, &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/static-web-apps/get-started-portal&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Building your first static site in the Azure portal&lt;/a&gt; for how to.&lt;/p&gt;
&lt;h2&gt;DocFx&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://dotnet.github.io/docfx/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;DocFX&lt;/a&gt; is a nice open-source tool to produce documentation from source code (including C#, F#, Visual Basic, REST, JavaScript, Java, Python and TypeScript) as well as raw Markdown files. It generates a static website from doc comments.&lt;/p&gt;
&lt;h1&gt;The deployment&lt;/h1&gt;
&lt;p&gt;First of all, we need a DocFx ready repo, ideally on GitHub. The simplest thing to do is to fork the &lt;a href=&quot;https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html#5-a-seed-project-to-play-with-docfx&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;DocFx seed project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once the repo is ready, follow the Microsoft documentation link above to create a new Azure static web app and select this repo from GitHub for Source in Deployment details.&lt;/p&gt;
&lt;p&gt;This will create a workflow in the repo. This is super useful as it automatically handles the triggers and required steps. The only step missing will be the building of the DocFx site. This has to be added just before the autogenerated &lt;code class=&quot;language-text&quot;&gt;Build And Deploy&lt;/code&gt; step.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;- name: Build Documentation
        uses: nikeee/docfx-action@v1.0.0
        with:
          args: docs/docfx.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that’s it.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This makes it very easy to have a website where you can share your API docs for other teams to refer to. And I remain very impressed with this static web app offering from Azure.
You may want to refer the &lt;a href=&quot;https://github.com/realrubberduckdev/docfx-seed&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;fork I created to test this on github&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[ARM deployment - Cannot find ServerFarm]]></title><description><![CDATA[ARM Template Deploying resources to Azure using ARM templates is generally quite easy given Azure’s native support in the generation of the…]]></description><link>https://rubberduckdev.com/arm-serverfarm-not-found/</link><guid isPermaLink="false">https://rubberduckdev.com/arm-serverfarm-not-found/</guid><pubDate>Fri, 18 Jun 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;ARM Template&lt;/h1&gt;
&lt;p&gt;Deploying resources to Azure using &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;ARM templates&lt;/a&gt; is generally quite easy given Azure’s native support in the generation of the template and availability of documentation. Although recently I got the following error and I spent more time than I thought I would, hence this post as a reference to myself and hopefully help others too.&lt;/p&gt;
&lt;h1&gt;The deployment error&lt;/h1&gt;
&lt;p&gt;The error I found while deploying was&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
    &quot;Code&quot;: &quot;NotFound&quot;,
    &quot;Message&quot;: &quot;Cannot find ServerFarm with name azure-functions-test-service-plan.&quot;,
    &quot;Target&quot;: null,
    &quot;Details&quot;: [
        {
            &quot;Message&quot;: &quot;Cannot find ServerFarm with name azure-functions-test-service-plan.&quot;
        },
        {
            &quot;Code&quot;: &quot;NotFound&quot;
        },
        {
            &quot;ErrorEntity&quot;: {
                &quot;ExtendedCode&quot;: &quot;51004&quot;,
                &quot;MessageTemplate&quot;: &quot;Cannot find {0} with name {1}.&quot;,
                &quot;Parameters&quot;: [
                    &quot;ServerFarm&quot;,
                    &quot;azure-functions-test-service-plan&quot;
                ],
                &quot;Code&quot;: &quot;NotFound&quot;,
                &quot;Message&quot;: &quot;Cannot find ServerFarm with name azure-functions-test-service-plan.&quot;
            }
        }
    ],
    &quot;Innererror&quot;: null
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The error says &lt;code class=&quot;language-text&quot;&gt;Cannot find ServerFarm with name azure-functions-test-service-plan.&lt;/code&gt; when I had the resource, also in the same resource group. Quite perplexing! After multiple attempts and then searching I found this &lt;a href=&quot;https://stackoverflow.com/questions/45630953/azure-cant-find-serverfarm/66554513#66554513&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure - can’t find serverfarm&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;The app service or function app being deployed has to be in the same region as the app service plan. And the error means it cannot locate the &lt;code class=&quot;language-text&quot;&gt;ServerFarm&lt;/code&gt; in the region of the app being deployed. So the solution is to ensure we are deploying the app to the same region as the app service plan.&lt;/p&gt;
&lt;p&gt;I realized this after coming across this comment in the StackOverflow post linked above.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Issue was that I accidentally had the Function App in a different Azure Region than the App Service Plan it was referencing. Worth noting that this error can be caused by that.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This was a simple solution or possibly a user error, thanks for me not knowing that the region matters. Hope it is useful for your deployment. Thoughts and comments welcome.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Create Front Door with Rules Engine]]></title><description><![CDATA[Azure Front Door Rules Engine Rules engine in an Azure front door allows us to customize how HTTP requests get handled. It is the go-to…]]></description><link>https://rubberduckdev.com/fd-rules-engine-creation/</link><guid isPermaLink="false">https://rubberduckdev.com/fd-rules-engine-creation/</guid><pubDate>Fri, 28 May 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Azure Front Door Rules Engine&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/frontdoor/front-door-rules-engine&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Rules engine&lt;/a&gt; in an Azure front door allows us to customize how HTTP requests get handled. It is the go-to place to add CORS rules or modify caching configuration based on incoming requests and so on.&lt;/p&gt;
&lt;h1&gt;Issue with deploying Azure front door with rules engine&lt;/h1&gt;
&lt;p&gt;This is where it gets tricky. For now, I use &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;ARM templates&lt;/a&gt; to deploy the front door (yet to look into Terraform/Pulumi/Bicep if they are any better at it). But this poses few issues&lt;/p&gt;
&lt;p&gt;On the very first deployment, I found&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Status: BadRequest 
Provisioning State: Failed 
Status Message: {&quot;status&quot;:&quot;Failed&quot;,&quot;error&quot;:{&quot;code&quot;:&quot;BadRequest&quot;,&quot;message&quot;:&quot;A resource reference was invalid: \&quot;Routing rule RoutingRule1 contains an invalid reference to RulesEngine: \&quot;/subscriptions/abcde-ghi-479e-959c-sdhfllk/resourceGroups/rg1/providers/Microsoft.Network/frontdoors/frontdoor1/rulesengines/RulesEngine1\&quot;\&quot;&quot;,&quot;target&quot;:null,&quot;details&quot;:null,&quot;additionalInfo&quot;:null}} &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then assuming, the rules engine need to be created first, I tried adding &lt;code class=&quot;language-text&quot;&gt;dependson&lt;/code&gt; only to get:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Status: BadRequest
Provisioning State: Failed
Status Message: {&quot;status&quot;:&quot;Failed&quot;,&quot;error&quot;:{&quot;code&quot;:&quot;InvalidResource&quot;,&quot;message&quot;:&quot;The property &apos;dependsOn&apos; does not exist on type &apos;Microsoft.Azure.FrontDoor.Models.DeepCreatedResource_1OfFrontdoorRoutingRuleEntityV2&apos;. Make sure to only use property names that are defined by the type.&quot;,&quot;target&quot;:null,&quot;details&quot;:null,&quot;additionalInfo&quot;:null}}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is because &lt;code class=&quot;language-text&quot;&gt;dependson&lt;/code&gt; is not valid in a routine rule. So now I am out ideas.&lt;/p&gt;
&lt;p&gt;On further investigation, turns out, it is a known bug, refer to the two below.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/MicrosoftDocs/azure-docs/issues/65782&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Samples to Create Front Door with Rules Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/MicrosoftDocs/azure-docs/issues/61497&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Document if/how a rules engine can be provisioned using an arm template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The suggested workaround&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/MicrosoftDocs/azure-docs/issues/65782#issuecomment-724416237&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;suggestion on the issue is&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Right now, the following workout may work (depending on your setup for automating):

* Create Frontdoor and Rules Engine Config FIRST, without having Front door reference the Rules Engine config
* THEN, make another ARM template call, on the same Frontdoor, but added with the reference to the rules engine.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Same idea, but a different implementation of workaround&lt;/h2&gt;
&lt;p&gt;I didn’t want to have two very similar ARM templates and also maintaining ARM templates can be a pain. So as a post-deployment step, I ran some &lt;code class=&quot;language-text&quot;&gt;az cli&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;az network front-door routing-rule update --front-door-name &quot;frontdoor1&quot; `
    --name &quot;RoutingRule1&quot; `
    --resource-group &quot;rg1&quot; `
    --rules-engine &quot;RulesEngine1&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So the original ARM template just deploys the rules engine but doesn’t reference it from routing rules. The referencing happens in this post-deployment step.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This was a simpler solution to the issue and worked better for me. Hope it is useful for your deployment. Thoughts and comments welcome.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Simple active-active configuration with Azure Front Door]]></title><description><![CDATA[Azure Front Door In this post, we will see a simple active-active configuration using Azure Front Door. As per Microsoft documentation…]]></description><link>https://rubberduckdev.com/azure-frontdoor-simple-active-active/</link><guid isPermaLink="false">https://rubberduckdev.com/azure-frontdoor-simple-active-active/</guid><pubDate>Sun, 11 Apr 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Azure Front Door&lt;/h1&gt;
&lt;p&gt;In this post, we will see a simple active-active configuration using Azure Front Door. As per &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/frontdoor/front-door-overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft documentation&lt;/a&gt;, Azure Front Door is a global, scalable entry-point that uses Microsoft global edge network to create fast, secure, and widely scalable web applications. So it does a lot more than what we are looking at in this post.&lt;/p&gt;
&lt;h1&gt;Active active configuration&lt;/h1&gt;
&lt;p&gt;In the world disaster recovery, an active-active configuration is really useful as both (at least 2) services behind a load balancer are constantly in use. So if one goes down, the other one keeps serving requests.&lt;/p&gt;
&lt;h2&gt;Architecture&lt;/h2&gt;
&lt;p&gt;In our architecture diagram below, we will use two azure functions to run in an active-active mode with an Azure Front Door acting as a load balancer.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1006px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ee1605cb7906179aeb468a5cba0e9ae8/aabec/architecture.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.781569965870304%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABPElEQVQoz5VRu04CQRTd//FLjJWlpa2VhSWl/oZWJFTWGjSRSMAEHxgsBAygZHcFlp2FnV3W2bn3mGHRiGLB7ebMuXMeY2GdIUATRVEyiyMGrP94KbFm/ouHSeI+7Lv2IwCLAWL8YhEtAP7anyO6MsRTPY/GTrEbvke8QjnjV5vixYm+jx8pQevyBJ3XC1R2S+OgH8NqBUnhWfQDBZOHVUoAji/fNg5vt06aPVcCUJrmOGtABh2e+dO5ilVx5UHJqdmxyakXy7l8O1cdbBad07KbyWa4eYiY7SMVd0zmse/7Qvy0rZQSo2HdHt05XjwNstDGNuubARr1Au63z1r+IGaLtGaipZ7TVIhgJsNEhlLKDDTNs76eoN09R23vSkQm8zrfzASE43bo9camIlrZ9tIsXRlfyvM83xsR8yfNWjtWgiGOfAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;architecture&quot;
        title=&quot;architecture&quot;
        src=&quot;/static/ee1605cb7906179aeb468a5cba0e9ae8/aabec/architecture.png&quot;
        srcset=&quot;/static/ee1605cb7906179aeb468a5cba0e9ae8/8890b/architecture.png 293w,
/static/ee1605cb7906179aeb468a5cba0e9ae8/1f316/architecture.png 585w,
/static/ee1605cb7906179aeb468a5cba0e9ae8/aabec/architecture.png 1006w&quot;
        sizes=&quot;(max-width: 1006px) 100vw, 1006px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Note that both the function apps are in different regions, meaning in case of a disaster taking out one region, the other can keep working. We can span it to more regions depending on the requirements.&lt;/p&gt;
&lt;h2&gt;Function app configuration&lt;/h2&gt;
&lt;p&gt;In our case, we are deploying an HTTP triggered with anonymous authorization (to keep the setup simple). In the response message, we just specify the region it is hosted in so we can see it in our logs when we test it.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d84c62b86d0ca1490fafc8f69c626ece/6de9d/uks-funap.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 40.955631399317404%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA8ElEQVQY033Ry26DMBCFYd4AXDAhHgfP+DLY2AToRVXf/8UqGrXKhn4621+zmMrkj3VdfZjG0fSXi3hpOym77pjseymlOFcZxGEYhGiG4WrM+ABaA2ilQCklz1Xvn1/LsjAzWRtTui85eG+M0UcPWuvbuSr58aqgrmulIC37/vpWSmFm65z9gYjjiapt267rmqYBAB+mmOY5xXniyDwFz8ET0mkshGgOAkCFmNZ9X7dtzqWUMufMPCHi7Z/40WsA50PJ+V5yTmlbyhyZnT3mbXDWEf6OvCUy5inWmogMIjPHiVOKwTtCQ4iEiH+feLr8DUIuOGrAjuEKAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;UKS funap 1&quot;
        title=&quot;UKS funap 1&quot;
        src=&quot;/static/d84c62b86d0ca1490fafc8f69c626ece/463a7/uks-funap.png&quot;
        srcset=&quot;/static/d84c62b86d0ca1490fafc8f69c626ece/8890b/uks-funap.png 293w,
/static/d84c62b86d0ca1490fafc8f69c626ece/1f316/uks-funap.png 585w,
/static/d84c62b86d0ca1490fafc8f69c626ece/463a7/uks-funap.png 1170w,
/static/d84c62b86d0ca1490fafc8f69c626ece/6de9d/uks-funap.png 1576w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;The anonymous authorization is to keep the setup simple when we test it.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 867px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3ad98ccf0f661662373547aa3a86bfc7/ad315/uks-funap2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 65.18771331058021%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABdklEQVQoz5WR7Y7aMBBF8wR17LAhEI+/xhMT7IUFAiwsVK2qSm3f/4GqJMtKrdiqe2SN7p+j8ehmU7O0foEhOWoVLiWoaoDfEEIM45W3yDnP5O7H6fxyPBz2+468DyHYgTzPOef5DcYY5/0c8xiyajqdzWZFMWGMCSHGKYQoy3Ly8ADaGmPnUhHRrJaIOAfjB6qqypTWiMgY+3TDe09E6FyMURvzuFrFmKSUAFDXdVmW411lWWaXr9/Pp+eUUowxpRRC0Fqj90iNNRrqOchayXqU/yLb7PfXy2W1XocQ2rYlIut60unb8vyTnq7N04tPnbpH1i3m1LSC8/HPeZ4jogKZjp+3X37tuuNu2/kQ4a4MdWmMlVKOrUwmE+ecViqud5vTldCiUQDy/maWj328tloUBSIarX0TQnwECaC06t89mf/JKDvszwYptX7He0/2noxWcXtYd4dNXBA6APhvmQgRfRNosUR01ugPbLa2L2soUoL6F78B2eZZzW2peCEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;UKS funap 2&quot;
        title=&quot;UKS funap 2&quot;
        src=&quot;/static/3ad98ccf0f661662373547aa3a86bfc7/ad315/uks-funap2.png&quot;
        srcset=&quot;/static/3ad98ccf0f661662373547aa3a86bfc7/8890b/uks-funap2.png 293w,
/static/3ad98ccf0f661662373547aa3a86bfc7/1f316/uks-funap2.png 585w,
/static/3ad98ccf0f661662373547aa3a86bfc7/ad315/uks-funap2.png 867w&quot;
        sizes=&quot;(max-width: 867px) 100vw, 867px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Azure front door and function app configuration&lt;/h2&gt;
&lt;p&gt;On the front door, we provide a hostname/domain name which is what the users will send requests to.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 560px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/825256a523d1b175c0189f1da2ec8513/360ab/fd1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.44368600682594%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAIAAADUsmlHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACQ0lEQVQ4y42SXW+bMBSGuV4WsI0Bm2+bUPNhQkK7LqGkC9BQVdUuermb3uxH7GJ/fgLaqJo6rY8s62DplQ+Pj3I6nZ6enh4eHpqmub+/H4ah67phGDabzWKxgBCC9zAnFNu2Pc9zXZdS6jojtm3Pu2maxsRcWJY11xBCVVUhhEpVVXd3d13XNU3TdX3bds3h0Lbt8Xjsuu5mommauq7npvq+r6pKCEEpVYQQVVXlWZbmhctzL5KWwwgh1sTbgr4yN4sxVhhjSZLEcew4DgAQQgTB+/95RpsAAChxHMsJxhiY0h9H8X0/ngjDEHyYlzDnPM/zLMs45wghjLFhGLquI4R0XR9djyd49nwGYzyGpZS73e7y6urqy/XhcLvf7+u63u12VVV9vb4uyipbX2ayTNO0LEspZZ7nUsogCDRNU6IoShPBWcBcK3QtxthqtYrjOIqiizg2qY9pqJs2BAAh9JcUhfFVuEooK8Tx58Xts26Q5fKzqmrqBATatN4XqSDdwDQgfB01z3z/A+kWguMlM/+xnabJdrMpZJYLVq3TNEnmN3z7pOeTt59j2HXdIAw55yHjPFp5njdLJpZpmQalhBBi2zadqvOEvdjebrdt13Z9f7j91rZ93/fDcNrv6+LyJt/W+XqbCCFlkWWZECJNUyllURSEkHHCfN/3XUeHGgYLXftkmSYhBGNDgxhArKpgOTH7W77y0rYfRkFcetmN/P5bPv7ClqOpy0kHmMxB9A/GsI4NbNrEi8JyCNa9jk344Qn/A3Gqejeflgg7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;fd 1&quot;
        title=&quot;fd 1&quot;
        src=&quot;/static/825256a523d1b175c0189f1da2ec8513/360ab/fd1.png&quot;
        srcset=&quot;/static/825256a523d1b175c0189f1da2ec8513/8890b/fd1.png 293w,
/static/825256a523d1b175c0189f1da2ec8513/360ab/fd1.png 560w&quot;
        sizes=&quot;(max-width: 560px) 100vw, 560px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;In the backend pool, we add both the function apps, keeping both &lt;code class=&quot;language-text&quot;&gt;priority 1&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;weight 50&lt;/code&gt;. So they will both remain active and continue servicing requests.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 545px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/578735eef985e58624c44652e20bebec/084e3/fd2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.02047781569966%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABh0lEQVQoz42ST6+iMBTFWZuA/YtQSsXHE5C2UOhUXRs3xmhi4vf/LBPojOObN4v5pYu7ufeee06D5/N5v99Pp9Pj8bjdbpfL5Xw+X69XznkURRBC8Ab8DcaYMRYURZHnOed8vRabTSFEXpZlURSEEIQQfgN9I7DWDsPQdf3HZ920qmm1UlpKOQyDlFLPyBmllDGm6zqttdcV2HEcjNlWzUrU6bqJsw9KKSGEUooxJoS8NpM3EEIQwqAqS8E5YwxB8Ov9i7+U+zrwYqy1SZL4heQ/8LqCuq6zLFssFt7M7zu94b5eLpcv/yfZx+NRKZWmKeecEAK/AgBYrVZZlvm5nPM4jtM0ZYxNzd2M1rrve0ppGIavToRQFEXbz60xJgxDjPF+v6+qSmvtnAMATDe3bbvb7ZRSfd83TfPHYYIRxLxEO1n33TTaixdC+OmBc24cR2PM4XBwzimlvBlzNhgCtJGx3Zsf1sVx7E2tqsofGAgh/A/LZ6bMvtiFaII4z/I8f6WVJInP+Sc0JU6ips4x/gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;fd 2&quot;
        title=&quot;fd 2&quot;
        src=&quot;/static/578735eef985e58624c44652e20bebec/084e3/fd2.png&quot;
        srcset=&quot;/static/578735eef985e58624c44652e20bebec/8890b/fd2.png 293w,
/static/578735eef985e58624c44652e20bebec/084e3/fd2.png 545w&quot;
        sizes=&quot;(max-width: 545px) 100vw, 545px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;We do a simple pattern match &lt;code class=&quot;language-text&quot;&gt;/*&lt;/code&gt; so all requests are directed to the only backend pool we have.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 543px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/18a02fdb257cf742ec407db0f423a990/8586d/fd3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 146.41638225255974%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAdCAIAAAAl5NuSAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAC9klEQVQ4y5WUS2/jRgyA55rGsWc0T41e1tuRR7JeHkt2YrQLH2KgwObQnhbbQ39O/0D/biGpawTZbVf7HQiSI4ociiK4XC6vr6/X67Xv+5fr9ePH10F5eWGMLZfL1VdACCdpmiaglAohKKWm4JxRx7Y815VSmqbJORdCmCOc80kZnhwlhBAopX758OF0OjX7XpV6kzeZKrORNE2VUnmeT2ZRFFmWxXFcFIVlWavVCrieFwaB57qESUilwewVRG8r/K+yEUIgCIIkiqWUrutIU2ADEYLJ98AjwHMdtX183KR5nodhSAidCSEEBGkRFk9EBoZBMOXIMOBsQNj91nz+O/35T+HGdrgjhCI03GcOwLZkFAWe6wT+2rZMjLExg3+D/SCo6rqqqrpuLMvGeFa3TNNkjAEpped56xEhxJxgMnbVMAyQJIkeORwOtv2DmSml1oiU8v8DvvaANE211kqptm19318sFsuRh4eHSU68dy6XECGwXq/jOB7mLEn8kSiKfN8Pw3AygyAIw9BxnOnI8zzfX7uOjRAEdV13I1rrruuen5/P5/PxeOy67ibP5/N+vz8ej89PTxtVhlmzjtXwqTjnlmUxxjDGQgg+Mv2kjDFKKeecMXZzIoQx96gdD5kJIe+aPM3t2xl+e8SG17FxlhBwHCdJkiiKpu5PTNnemTcnpXQaRBCGYZIkSinG2N3d3f3ITyOTcjPf8rC4RxCCzSYt8rwsy6Ioqqo6HA5VVWmt+75vmuZwOGit9/t913Wn00lr3bZt33dVoxkXYLtr67ar6yrLtkqpuq7LsmyaZpK73a4oiqZptNb1F6qqcsMtYRJgJjHlBkKGYWCMIYS3ar9Z8MRicQ9Xq+HOW5VnW/X4mOX5sOJs27a+h+04jHMgGLaFYZvMtqQphJRSzEBKSSkFUd5Xlz827cW0PWY6CKEfWENB82v5+1/x6RO3fGu9wRgjOHsNrV0r9mXkO44pEFzheRBChiHx/aDZ67ppy7Kav3SnZfAPJdS4hI/ZYvcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;fd 3&quot;
        title=&quot;fd 3&quot;
        src=&quot;/static/18a02fdb257cf742ec407db0f423a990/8586d/fd3.png&quot;
        srcset=&quot;/static/18a02fdb257cf742ec407db0f423a990/8890b/fd3.png 293w,
/static/18a02fdb257cf742ec407db0f423a990/8586d/fd3.png 543w&quot;
        sizes=&quot;(max-width: 543px) 100vw, 543px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;We can use a small PowerShell script that keeps posting requests to our front door URL.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$requestBody = @&apos;
{
    &quot;name&quot;: &quot;DP&quot;
}
&apos;@

do {
    $response = Invoke-WebRequest -Uri https://fdtst.azurefd.net/api/HttpTrigger1 -Method POST -Body $requestBody
    Write-Host $response.Content
} while ($true)

$response = Invoke-WebRequest -Uri https://fdtst.azurefd.net/api/HttpTrigger1 -Method POST -Body $requestBody
Write-Host $response.Content&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When we first start running the script, we see it keeps getting a response from the UK south, mainly because I am closest to that data centre.
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 661px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d37538de66611a864dfd4bb171127fcb/d5cf8/script-log1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.54266211604096%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7CAAAOwgEVKEqAAAABC0lEQVQoz3WSSaqEYAyEvYSKCs7zPII7vf+l0nyBPN6iexEy/ElVmeic5ylhGMo4jlKWpVRVJU3TaI4NwyD3fcs0TWrrumrPsiya932v8b7vOucw4LqugqVpKnEcq2coyzL1x3Gop6frOkmSRNq2lbqutUZMndiBPYoiZaEBFhpMwTzP8jyPKqPGFzF8XZds26bvxBhqHR5RWBSFMptC8jzPlRUwPDXI6IEc1fQQg0P8pxAm1PGIoQZGVL7vq6D0oBDQnwpZvO/7CoJC9masticGTdH/HSLAdsgt+AIH5CAItMAQRZogIqfODu3qBs5VUUeNGFOFDHiepyC2Q7su4Hj7VezK7PjblZn7ADRe65yCFTLPAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;script-log1&quot;
        title=&quot;script-log1&quot;
        src=&quot;/static/d37538de66611a864dfd4bb171127fcb/d5cf8/script-log1.png&quot;
        srcset=&quot;/static/d37538de66611a864dfd4bb171127fcb/8890b/script-log1.png 293w,
/static/d37538de66611a864dfd4bb171127fcb/1f316/script-log1.png 585w,
/static/d37538de66611a864dfd4bb171127fcb/d5cf8/script-log1.png 661w&quot;
        sizes=&quot;(max-width: 661px) 100vw, 661px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Then we go ahead and stop the UK south function app to simulate an outage from the region.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 333px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/60f27b6499f896cfe35012c4f7fffbce/50822/stop-uks.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.45733788395904%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACBUlEQVQoz2WR6W7aQBCADSaXarOnD3xhx+sFr69wxIAJDhSaCrVVSqpUifojUp+jD19B0ipKPo1GMxp9q51dCe2BAEJFUVRVPbQIAKAeCAnJdC2mNMbYJwQiCNptRVHaACCEJEyI0c1ikc2r6Xh4YXQs6vTzPK+vF1ej4ZrxuNisxpffVstPjOteXBTFalkXeaprVMLUwGZgWm4kBmEvQ7oHDN/2eRgXnImPfph60dgPlmn6mfWhHjh+jycDu8uR7kgfsCM7qyN/26CDllme2rMTa9IyygYdNfWx5VecVcF55fsT+3x+6sxaZtnQRsedyZlTSWdt4yR5gos/4fBLvvyV1Q/B6Daa3LHLXVzdl9vfcf0YVT/PJz+Gm6fi+pFdfo/KXVY/pIsHScG2TLMm7Ddgv4FEEwuZpDJJWzSTSXqkZYc2kUlyrOVNnDyHTPZZQgi5rrtZr7fb7d1uJ4RQVRXvfwAidIiXYg/GGP0D718bE9M0Pc/19rimaVJKiWZgohFCCaWaYVGjgwlB75A0TYvjOIoiznmapkkiRHrh9qeuqFk/16zA4HPqDzDtYAjeyoQQxlgQBOTV2RBBjIkCgM9YTwiR54ZtqwDg9zLnPAzDw5rwte8itEiSm7K8ret5HJuvx//lbrfb6/We/RcTYwrAtWWth8Ov0+nNaHQ/m1153pt7/wVPblZACBYegAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;stop-uks&quot;
        title=&quot;stop-uks&quot;
        src=&quot;/static/60f27b6499f896cfe35012c4f7fffbce/50822/stop-uks.png&quot;
        srcset=&quot;/static/60f27b6499f896cfe35012c4f7fffbce/8890b/stop-uks.png 293w,
/static/60f27b6499f896cfe35012c4f7fffbce/50822/stop-uks.png 333w&quot;
        sizes=&quot;(max-width: 333px) 100vw, 333px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;That immediately results in failing requests and this happens until the front door realizes that the backend pool is no longer available.
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/36a13afb701b612f385abba524e11d57/52b2d/script-log2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 15.358361774744028%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAIAAAAcOLh5AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAiklEQVQI1x3GSQ7CMAwF0B4gduLhO61SJaBSOrDg/rdD4q3eNCNWRG/t2ceyLAACqAATCbEwp5SUWOkfZkqJiZSYE01VdXOsokNtKbKqnahNNHI5LYYoUdrMLw9hDs5vRxetuXTRqZl/Y25ZepHb44M4zG+Pw3A4bo/dsBte5pv5Q+w0XB5DFTn/AB8oDyfMPJhgAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;script-log2&quot;
        title=&quot;script-log2&quot;
        src=&quot;/static/36a13afb701b612f385abba524e11d57/463a7/script-log2.png&quot;
        srcset=&quot;/static/36a13afb701b612f385abba524e11d57/8890b/script-log2.png 293w,
/static/36a13afb701b612f385abba524e11d57/1f316/script-log2.png 585w,
/static/36a13afb701b612f385abba524e11d57/463a7/script-log2.png 1170w,
/static/36a13afb701b612f385abba524e11d57/e1fbe/script-log2.png 1755w,
/static/36a13afb701b612f385abba524e11d57/52b2d/script-log2.png 1794w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;The azure front door keeps trying UK South, while now serving requests via UK West.
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 742px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/186ce2f8cac9936240750c79adf155f3/14945/script-log3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 21.501706484641637%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAIAAAABPYjBAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAv0lEQVQI1wXB7bKCIBAAUP8mlZmyLOzyoQLCQOPc2/u/W+cMMcVaK1ubS/EhHMdRaz1i/P9+W2ullNZaSql/Pmcpvffruph5HMf74zFk5/u2ZzTNOou47/uZ87Ftf9fVW8splfP0zjERGcNElhlWKW7jXYgBUCORWWUwhEq9lVosS+9l8MAstQbnXogT4gtgRpyknBBnoueyDEKIScrnPBuFXqF+L9HQSTayRSlxWTVAQhNQawBC7RQWstFQAPUDLfwbAJp+PloAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;script-log3&quot;
        title=&quot;script-log3&quot;
        src=&quot;/static/186ce2f8cac9936240750c79adf155f3/14945/script-log3.png&quot;
        srcset=&quot;/static/186ce2f8cac9936240750c79adf155f3/8890b/script-log3.png 293w,
/static/186ce2f8cac9936240750c79adf155f3/1f316/script-log3.png 585w,
/static/186ce2f8cac9936240750c79adf155f3/14945/script-log3.png 742w&quot;
        sizes=&quot;(max-width: 742px) 100vw, 742px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;And finally, once Azure front door establishes that UK South is unhealthy, it sticks to UK West.
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 667px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0d10b127476dd8ca03200099c65674b8/28f1c/script-log4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 146.07508532423208%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAdCAIAAAAl5NuSAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACyElEQVQ4y4XV2XLqOBAGYB4iwSniYBlLam3d2myyXST4/R9qxu4qw5CcM30FKhl9av0yO2MMIh4OB+9913VqLUTUWk/T5L231oYQlFLOOf5Aaz08POyGYQCApmkA4Hg8dl13Op201n3fj+OotZZSGmP6vgcAY8wwDNZa7/3j4+OOiMZxbNs25yyl1Fo754jIWjvPcyklxjhNUwhhXIuIzufzNE1N0+ystUR0OBwQUQih1yIiADifz4jonENEpVQIgYi01nGtZWVm7/d7AHh5eWG2lFIIwexhGJittQYA/uqc+4UNANbaGKMxZp7nWitPCCHUWv/G3n6e2a+vr4jovWctIvJ4zjml9Du773ullBCi1qqU4gncDgA4nU5XNiLedZstxpjL5VJKIaJaq/ee2Yg4TdM4jk9PTzve4W23mW2MuWNztwEgrXVl34bkJ9sY85O9JAwRa61t26aUpJQcQ155nuecM3d7YxPRle2cizE+Pz8TEWeQYcaYt7c3Itq0RMRHmNda2Jzk/X6vtd66zSH5e7fv2cMwKKWstYgIAJfLJefME7z3pZRa69btJSTe+5QSs0+nE1+dlJK19v39PcYYQuBdxBh5vJTyRzYLhRClFGZrrbuu4zvDfbHWLuwQQinlV/b393dKiSc458paHKp7doxxu/Hs3NgxRgBgtjHmnt00DbOFEHw9+r4vpfCVvGNfu80qXnkYBiklv7S42yklRPzJrrUuIfHe55x5z1u3c87W2o+PD36YtSklHq+1llL+yFZKMftnSP7D5pVv2caYEILWeus2L8ip5FdKrXXpNm9pOypjDMfAOff5+cns7WEe5z0vbO4th6RtW7EWX8mc821IlFJbSK7s23jyO9B7r5T6+vqKMfK+rLXcMD6dUsrCvk0Y35g7NhH9D7tpGill27bH41EIIaXsuo7ZfBzb39iWwn/Z/wBxGRO8Yg6goAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;script-log4&quot;
        title=&quot;script-log4&quot;
        src=&quot;/static/0d10b127476dd8ca03200099c65674b8/28f1c/script-log4.png&quot;
        srcset=&quot;/static/0d10b127476dd8ca03200099c65674b8/8890b/script-log4.png 293w,
/static/0d10b127476dd8ca03200099c65674b8/1f316/script-log4.png 585w,
/static/0d10b127476dd8ca03200099c65674b8/28f1c/script-log4.png 667w&quot;
        sizes=&quot;(max-width: 667px) 100vw, 667px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This was a simple setup to test how to implement an active-active configuration. This can be done in a far better way with an understanding of Azure SLAs to ensure we get a high availability guarantee.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Terraform - sharing modules across organization]]></title><description><![CDATA[Terraform modules In Terraform world, modules are a way of reusing infrastructure configuration. We could create a module as a lightweight…]]></description><link>https://rubberduckdev.com/tf-module-consumption/</link><guid isPermaLink="false">https://rubberduckdev.com/tf-module-consumption/</guid><pubDate>Sat, 10 Apr 2021 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Terraform modules&lt;/h1&gt;
&lt;p&gt;In Terraform world, modules are a way of reusing infrastructure configuration. We could create a module as a lightweight wrapper around a single resource or as a combination of multiple resources which need to be deployed regularly.&lt;/p&gt;
&lt;h1&gt;The idea&lt;/h1&gt;
&lt;p&gt;I mostly specialize in Microsoft technologies and Azure cloud, so this post will circle these technologies. The team I work in currently own multiple Terraform modules and we are working towards adopting the model shown below.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/43de107845dd3414577ae9fd39900944/da9c1/workflow.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.85324232081911%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAABYlAAAWJQFJUiTwAAABXElEQVQoz4WRPU/DMBCG86eZWBEbv4GpGwMSLGVAIBRUaCWqqhC1pV9ENHFSJ8GpE8fx3SG3VQVVBSd78PCcn3vPof9KGyi0UTXg9klKMr3yEcH5myw0RHmVKojirNNvNfsXo2j82Tsf3B8hokOEhICEaC8BEgDas/7oq6yFMrZLWQXLcMbHfJUUhRa53MA/DbGq0YBtURnUgFzqvLIwHvJy8rjHOsc33f7ZXZhKTUST6ezpuV1Kednjp82ZKGsLI26FNgVg4XTpT/rXScbD3HwpKArpuq7nvRnA2mAu62KtbXA9iJ0Ldxb7gaWZCEIWL5NCVURUViZdaamMkDoVGROLtFTMa/jtE9qmjWC91nAURb7vz+dzIcTGVlWgaori7PHl4arb8BavSTD8GLmA6AzfB7P5lHPOE84YU0od3NnO1S5lp90a3o58L2JxEARJkgBsI/lF7jcCtLL0DVgoPOjebWsWAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;workflow&quot;
        title=&quot;workflow&quot;
        src=&quot;/static/43de107845dd3414577ae9fd39900944/463a7/workflow.png&quot;
        srcset=&quot;/static/43de107845dd3414577ae9fd39900944/8890b/workflow.png 293w,
/static/43de107845dd3414577ae9fd39900944/1f316/workflow.png 585w,
/static/43de107845dd3414577ae9fd39900944/463a7/workflow.png 1170w,
/static/43de107845dd3414577ae9fd39900944/e1fbe/workflow.png 1755w,
/static/43de107845dd3414577ae9fd39900944/da9c1/workflow.png 1798w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;This image is from a &lt;a href=&quot;https://moimhossain.com/2020/11/27/azure-resource-governance-with-template-specs-biceps/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;brilliant post&lt;/a&gt; by &lt;a href=&quot;http://en.gravatar.com/mdmoimhossain&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Moim Hossain&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The idea is, one team, the Platform team in this case (different organizations can have a similar team named differently say, Infrastructure team, DevOps team etc.) can design modules while liaising with security, management and other stakeholders. These modules should be designed generically, &lt;a href=&quot;https://www.terraform.io/docs/extend/testing/unit-testing.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;tested&lt;/a&gt;, &lt;a href=&quot;https://www.rubberduckdev.com/terraform-static-analysis/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;security analysed &lt;/a&gt; and then shared for consumption across product/development teams.&lt;/p&gt;
&lt;p&gt;Apart from true IaC and GitOps, the main advantages of this model are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ownership&lt;/strong&gt;: Resource deployment ownership lies with product teams. While the modules ownership lies with the Platform team.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conventions and verifications&lt;/strong&gt;: We have verified configurations and conventions across the organization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control and sanity&lt;/strong&gt;: Platform team can maintain the overall sanity of infrastructure with controls in place such as &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/governance/policy/overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure policies&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agility&lt;/strong&gt;: Product teams can drift away from modules under special circumstances. Or use other deployment technologies such as &lt;a href=&quot;https://www.pulumi.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Pulumi&lt;/a&gt;, &lt;a href=&quot;https://github.com/Azure/bicep&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bicep&lt;/a&gt; or &lt;a href=&quot;https://devblogs.microsoft.com/powershell/announcing-the-preview-of-psarm/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;PSArm&lt;/a&gt; etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Terraform module source&lt;/h1&gt;
&lt;p&gt;When consuming a module in Terraform we need to specify a source.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;module &quot;resource_group&quot; {
  source = &quot;./../modules/ResourceGroupModule&quot;
  name     = &quot;tfmoduletest3&quot;
  location = &quot;North Europe&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice the &lt;code class=&quot;language-text&quot;&gt;source&lt;/code&gt; parameter. Here it takes a local path to a terraform module. Terraform supports different &lt;a href=&quot;https://www.terraform.io/docs/language/modules/sources.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;modules sources&lt;/a&gt;. The one that we are interested in is &lt;a href=&quot;https://www.terraform.io/docs/language/modules/sources.html#generic-git-repository&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;generic Git repository as a module source&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Creating and consuming Terraform modules&lt;/h2&gt;
&lt;p&gt;There is enough &lt;a href=&quot;https://www.terraform.io/docs/language/modules/develop/index.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;documentation&lt;/a&gt; regarding the creation of Terraform modules and I am going to focus mostly on our approach towards consumption of modules across multiple teams.&lt;/p&gt;
&lt;p&gt;See the examples on &lt;a href=&quot;https://github.com/realrubberduckdev/tf-module-test&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Github&lt;/a&gt;. A very simple module can look like&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;variable &quot;name&quot; {
  type = string
}

variable &quot;location&quot; {
  type = string
}

variable &quot;tags&quot; {
  type = map
}

resource &quot;azurerm_resource_group&quot; &quot;resource_group&quot; {
  name     = var.name
  location = var.location
  tags = var.tags
}

output &quot;name&quot; {
  value = azurerm_resource_group.resource_group.name
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the way to consume it will be&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;provider &quot;azurerm&quot; {
    features {}
}

locals {
  tags = {
    &quot;key1&quot; = &quot;value1&quot;
  }
}

module &quot;resource_group&quot; {
  source = &quot;git::https://github.com/realrubberduckdev/tf-module-test.git//modules//ResourceGroup?ref=9451c9a386b411b71400aa1a382c3e93b2b9e9a0&quot;
  name     = &quot;tfmoduletest3&quot;
  location = &quot;North Europe&quot;
}

module &quot;storage_account&quot; {
  source = &quot;git::https://github.com/realrubberduckdev/tf-module-test.git//modules//StorageAccount?ref=9451c9a386b411b71400aa1a382c3e93b2b9e9a0&quot;
  name     = &quot;dpteststrgacc1&quot;
  resource_group_name = module.resource_group.name
  location = &quot;North Europe&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice this in &lt;a href=&quot;https://github.com/realrubberduckdev/tf-module-test/blob/main/ThisFolderCanBeInAnotherRepo/main.tf&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;main.tf&lt;/a&gt; on the GitHub repo. The git as module source lets us use a git repo we have access to while locking down to a specific commit hash. So product teams can use these modules in their repo while ensuring, they do not automatically pick up breaking changes.&lt;/p&gt;
&lt;h2&gt;Possibilities&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Semver&lt;/strong&gt;: The Platform team can use tools like &lt;a href=&quot;https://gitversion.net/docs/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GitVersion&lt;/a&gt; to adopt &lt;a href=&quot;https://semver.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;semantic versioning&lt;/a&gt; and automate the module versioning using git tags. So there references do not need to be an unreadable hash, rather can be &lt;code class=&quot;language-text&quot;&gt;ref=v1.0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Templates&lt;/strong&gt;: With proper cost implication investigations, the Platform team can group resources into being resource templates. Thus reducing overall cost for the organization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dotnet tool&lt;/strong&gt;: With a simple dotnet tool, we can adopt an organization-wide convention regarding &lt;a href=&quot;https://www.terraform.io/docs/language/state/index.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;state file management&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This post is more about the concept and the workflow model and how Terraform helps us with the model adoption. Surely there will be other better technologies for &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/learn/what-is-infrastructure-as-code&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;IaC&lt;/a&gt; and the quest to find a better one shall continue. Hopefully the one without state file management (more on this in a separate post). I hope this was useful, please do share any thoughts or comments you might have.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Cosmos DB TransactionalBatch - Dealing with BadRequest StatusCode]]></title><description><![CDATA[Introduction Azure Cosmos DB provides a great way to perform transactional batch operations using the .Net SDK. This operation method…]]></description><link>https://rubberduckdev.com/cosmosdb-transactional/</link><guid isPermaLink="false">https://rubberduckdev.com/cosmosdb-transactional/</guid><pubDate>Sat, 27 Mar 2021 12:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Azure Cosmos DB provides a great way to perform &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/cosmos-db/transactional-batch&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;transactional batch operations using the .Net SDK&lt;/a&gt;. This operation method provides the &lt;a href=&quot;https://en.wikipedia.org/wiki/ACID&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;ACID&lt;/a&gt; guarantees and so it makes it much easier to perform multiple steps in one transactional batch or fail and roll back the whole operation.&lt;/p&gt;
&lt;h1&gt;Dealing with BadRequest StatusCode&lt;/h1&gt;
&lt;p&gt;The Microsoft documentation link above does a fantastic job of explaining how to implement a transactional batch operation, so won’t go much into that. But when I was trying it out, I kept getting &lt;code class=&quot;language-text&quot;&gt;StatusCode=BadRequest&lt;/code&gt; and it can be quite difficult to find out why that happens.
&lt;br/&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 546px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9b8cfe4171bab0133514619f9d7b07d3/4304f/exception.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35.83617747440273%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAA7DAAAOwwHHb6hkAAABT0lEQVQoz3WRW2vcMBCF/ZuSFkIh8d3ri1b2yJJs7xqnpe1uS14L/flfsQzNS/vwcYaBORrNiey84d2EEUtV1RTFibI80TQdw2Cw1gcdR3dgLN7P9L3w9PSJh4cPPD5+DOx1ZGRkWVaMscEoTYtAlpV0XY9SmrY903U60DSKvj8eELGh1lrQ2mCMI5qmhW9fb9zvP3F2Ik1yst00ybksK5OfGY3DuZlx9AzBzKNUH8ybVh3aqNCLxE5c1lf8csXYicFYiqrhOclx8xWlhbo9vw91x8b7L+I4J0neieOMaNHC7bpxXzde3cRQ1bRxRhNnXMUylDX1SxI2PoYO3U/yL6I38fzavvB7+8xND2xVzSUvcc8JP2TkzXq+1y15kpNm5d8b/49IlOViZ1a/MItFujOqqjmlBUZpJrF4seGOewjDMCI7YkMIImMIdO/necUf9uwJNTYOr2oAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Exception message&quot;
        title=&quot;Exception message&quot;
        src=&quot;/static/9b8cfe4171bab0133514619f9d7b07d3/4304f/exception.png&quot;
        srcset=&quot;/static/9b8cfe4171bab0133514619f9d7b07d3/8890b/exception.png 293w,
/static/9b8cfe4171bab0133514619f9d7b07d3/4304f/exception.png 546w&quot;
        sizes=&quot;(max-width: 546px) 100vw, 546px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;
I have been unable to find out details from the response object.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;var result = response.GetOperationResultAtIndex&amp;lt;Customer&gt;(0);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Even the OperationResult wasn’t helpful, check the image below. Note that, &lt;code class=&quot;language-text&quot;&gt;Customer&lt;/code&gt; is the object I am trying to write to the database.&lt;/p&gt;
&lt;p&gt;Say we have a function &lt;code class=&quot;language-text&quot;&gt;GetResponse&lt;/code&gt; like below:
&lt;br/&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 244px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5c8b4b486699d582940c7cdebbb10361/f3e72/operation-result.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.49180327868852%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAB2UlEQVQozzWQ2XLiMBRE+a0J2LIsGw9ZSMLiRZttlmwMgZCkJpWfP1M2mYeu7rpXfaTSIAwlq9UG71rWmwdWzQoRSoSI6Xbh/yzi3qVMkPEY72qsdf0+jtNeQkgG3aBt1xjboI3HGkdVWZz1bB93/QXdvKlbnKt7QDK+pPZtf2acZkRRglJjgkCcgU2zwvsVbduVHVo7yrLi+vqWm5u73qfTO66upkSRIlYZRjtq3yClQsruhT9AGSnaDuha2qbt3VS21+T3JWmSkai0/4ZIKGKZ9GWrLUZbRBij4rRX2AFVdoldb6nqNW61oWrWLLTDtlu2uz3Nwwu5Noy6gsp6H6mM0jUU1nMRKQI1JkwyLsKIQRGnvC8LPkrDZ2V4nS35cjUfeU4+DCmGAeUopApEry5rEXPKSz6N56/WfFmPDQU3F4LB468hR+vYm5qj8+yN59VYdqXhYb7gXkjmUjGLYubR2fPxhDdX8/30zMHV/KkMz3nJe6IYvAwDjtqzns5505a3yvI8W7DPK/bLgiJSVHH6o4RlGLEIY15mSw6lZrcoOVSa12XBUUoGRTahzTeYyT2HsuDpfs72dsa7MX3eLQpO1nEynpPWmDQ7QwPBfBSePRAsR4KpiPgHYY1glyIieDMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Operation result&quot;
        title=&quot;Operation result&quot;
        src=&quot;/static/5c8b4b486699d582940c7cdebbb10361/f3e72/operation-result.png&quot;
        srcset=&quot;/static/5c8b4b486699d582940c7cdebbb10361/f3e72/operation-result.png 244w&quot;
        sizes=&quot;(max-width: 244px) 100vw, 244px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Finding out the issue&lt;/h2&gt;
&lt;p&gt;The only way I could find out what the issue was by completely ditching the &lt;code class=&quot;language-text&quot;&gt;TransactionalBatch&lt;/code&gt; method and test it using&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;await container.CreateItemAsync&amp;lt;Customer&gt;(customerItem, new PartitionKey(customerItem.Id));&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And this now led me to another exception, which was a bit easier to understand.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;PartitionKey extracted from document doesn&apos;t match the one specified in the header on CreateItemAsync&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Explanation&lt;/h2&gt;
&lt;p&gt;So if you have followed the Microsoft documentation, &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/cosmos-db/transactional-batch#limitations&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;it lists two limitations&lt;/a&gt;. The bit that it doesn’t list, which seems to be by design is a &lt;code class=&quot;language-text&quot;&gt;TransactionalBatch&lt;/code&gt; can only work in a single &lt;code class=&quot;language-text&quot;&gt;Partition&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey(customer.PartitionKey))&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So when we try to get the container, we have to specify the &lt;code class=&quot;language-text&quot;&gt;PartitionKey&lt;/code&gt; too as the second argument.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;var container = await database.CreateContainerIfNotExistsAsync(&quot;CustomerInfo&quot;, &quot;/id&quot;);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And both the partition keys need to be the same, otherwise &lt;code class=&quot;language-text&quot;&gt;TransactionalBatch&lt;/code&gt; will keep throwing a &lt;code class=&quot;language-text&quot;&gt;BadRequest&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;This &lt;a href=&quot;https://stackoverflow.com/questions/58121736/partitionkey-extracted-from-document-doesnt-match-the-one-specified-in-the-head&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;stackoverflow post&lt;/a&gt; touches on the solution. So overall we need to ensure our &lt;code class=&quot;language-text&quot;&gt;Customer&lt;/code&gt; class has a &lt;code class=&quot;language-text&quot;&gt;PartitionKey&lt;/code&gt; which Cosmos DB can serialize.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;public class Customer
  {
    [JsonProperty(PropertyName = &quot;id&quot;)] // This is needed by Cosmos DB
    public string? Id { get; set; }

    [JsonProperty(PropertyName = &quot;CustomerPartition&quot;)] // This is needed by Cosmos DB. Note that it cannot take any special characters, not even hyphen.
    public string? CosmosPartitionKey { get; set; } = &quot;CustomerPartition&quot;;

  }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Along with this we also need to ensure the &lt;code class=&quot;language-text&quot;&gt;Container&lt;/code&gt;, as well as &lt;code class=&quot;language-text&quot;&gt;TransactionalBatch&lt;/code&gt;, are pointing to the same &lt;code class=&quot;language-text&quot;&gt;PartitionKey&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;string partitionKey = &quot;CustomerPartition&quot;;
var container = await database.CreateContainerIfNotExistsAsync(&quot;CustomerInfo&quot;, $&quot;/{partitionKey}&quot;);
TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey(partitionKey))&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saves you some time if you are trying this out. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Mocking HttpClient for unit testing]]></title><description><![CDATA[Introduction HttpClient is an easy way provided by dotnet to send http requests. But the problem is that it doesn’t implement an interface…]]></description><link>https://rubberduckdev.com/mock-http-client/</link><guid isPermaLink="false">https://rubberduckdev.com/mock-http-client/</guid><pubDate>Tue, 19 Jan 2021 18:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;HttpClient&lt;/a&gt; is an easy way provided by dotnet to send http requests. But the problem is that it doesn’t implement an interface we could mock for unit testing.&lt;/p&gt;
&lt;h1&gt;How to mock HttpClient&lt;/h1&gt;
&lt;p&gt;Say we have a function &lt;code class=&quot;language-text&quot;&gt;GetResponse&lt;/code&gt; like below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;public async Task&amp;lt;string?&gt; GetResponse(HttpClient httpClient, string requestUri)
{
        var request = new HttpRequestMessage
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri(requestUri)
        };

        using var response = await httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();
        string? responseBody = await response.Content.ReadAsStringAsync();
        return responseBody;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Moq for mocking&lt;/h1&gt;
&lt;p&gt;Let’s use &lt;a href=&quot;https://github.com/Moq/moq4/wiki/Quickstart&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Moq&lt;/a&gt; for mocking. If you notice the method we would like to stub is &lt;code class=&quot;language-text&quot;&gt;SendAsync&lt;/code&gt;, which is a protected method in &lt;code class=&quot;language-text&quot;&gt;HttpClient&lt;/code&gt; class. The way to mock that is:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;public static HttpClient GetMockedHttpClient(string responseContent)
{
    var handlerMock = new Mock&amp;lt;HttpMessageHandler&gt;();
    var response = new HttpResponseMessage
    {
        StatusCode = HttpStatusCode.OK,
        Content = new StringContent(responseContent),
    };

    handlerMock
        .Protected()
        .Setup&amp;lt;Task&amp;lt;HttpResponseMessage&gt;&gt;(
            &quot;SendAsync&quot;,
            ItExpr.IsAny&amp;lt;HttpRequestMessage&gt;(),
            ItExpr.IsAny&amp;lt;CancellationToken&gt;())
        .ReturnsAsync(response);
    var httpClient = new HttpClient(handlerMock.Object);
    return httpClient;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice how the &lt;code class=&quot;language-text&quot;&gt;SendAsync&lt;/code&gt; method has now been stubbed and this can be used in unit tests.&lt;/p&gt;
&lt;p&gt;So in a unit test we can call &lt;code class=&quot;language-text&quot;&gt;GetResponse&lt;/code&gt; as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;var mockedHttpClient = GetMockedHttpClient(&quot;{}&quot;);
var response = someObject.GetResponse(mockedHttpClient, &quot;some-uri&quot;);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saves you some time if you are trying this out. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Azure project during the holidays of Dec 2020]]></title><description><![CDATA[Introduction In recent holidays at the end of 2020, I thought of brushing up my dotnet core web development skills or to be honest learn…]]></description><link>https://rubberduckdev.com/where-streaming-architecture/</link><guid isPermaLink="false">https://rubberduckdev.com/where-streaming-architecture/</guid><pubDate>Fri, 01 Jan 2021 08:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In recent holidays at the end of 2020, I thought of brushing up my dotnet core web development skills or to be honest learn about it. I have never been a professional web developer. My UI development skills still suck, but I learnt quite a bit about the whole architecture which is what I plan to share in this post. &lt;/p&gt;
&lt;h1&gt;The idea&lt;/h1&gt;
&lt;p&gt;The overall idea is to create an online service which can tell us where a certain tv show or movie is streaming. So, for example, we can search for &lt;a href=&quot;https://www.imdb.com/title/tt6723592/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Tenet&lt;/a&gt; and it will find it for us where it is streaming, say Amazon prime for example.
I managed to deploy the &lt;a href=&quot;https://wherestreaming.online&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;website&lt;/a&gt; with a free domain, thanks to &lt;a href=&quot;https://www.freenom.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;freenom&lt;/a&gt;. Registration and binding of a custom domain with the azure app service can be a post by itself, maybe one for later.&lt;/p&gt;
&lt;h1&gt;Architecture diagram&lt;/h1&gt;
&lt;p&gt;Planning to share the overall architecture of the project, which is as follows:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 790px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/92608eaf26aa6f29701401746d598a32/ff2de/architecture-diagram.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 91.12627986348123%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAIAAADUsmlHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAC0UlEQVQ4y5WSy28PURTH54+xsLOXsCESYSEs2BAiRBckNopUKKkIkVDCxrNabUijiao+EI/WI5FfkxbV9tdW6/c0M787v3nce+eee8+RmaHRxoK7mszczzmfOedr0e+DhETk8yhfKeYZm66H3Qss78fpp78faxnsRPWv9UpfYf5x8ce29/M9hYCINP4bXAlqOXvuaXEhV6u/dflnT/5H52rIrn563jT64dH3ypUZd7AcEZHBxTtLClkKIAzDIAy1MUQkQE3XquOOM+tFc0FsC0jgDEMkXCJhaa2FEDziMlYKNIBJHIB8FsowBKVipRVopQAQJxhM/BBoDKZVrOFvftPzSqGuEivMxLKxB34UZDq/Gnru/VdTF3I+kQGdvLf6v9i77o1NlEMiUulYEbHvbq79/BvfFX7g1WwHiYJnA8XDDaX9O4PuTkOkQScwl7LqMA1JZ6WSeiP9U0e3th9af/NG8wunZs/PfdNEfm/Pwp4dhYbdXmebXoSTn6Rfaip9fjeYP7at4/DG29ePD2QVVaw1Y9HIa3bvlq6UAFFn2lGs31cjT4AxqA0hmjAMhh6M9t7OhZ7I6upqxb1y0e95aF86z25c47MzJu1meSEfmCzWBRCSQZQyjnjC8Fi4NVeKGJF0rOT3BTE9yT+Py8kJ6diZrYWYSGidDDBW2nY9zgWXUKqwWMqIx5GII6m4Rm5IEHEiSaQMAmjrz6XLOInE2FTh4Nn2A6fbBkbG03j9zoYM6GUr9bdQ3ynKv1ZL4omoQCPimp0trR2D/cNjKzcfnS85rmMHvp/cYGU8sxpPrDJHVtCTc7AMRiSHBWt3tXydLWuDG/ZdGB6dFkKEkVBEqlrSrY1wqREuH8HBriXwova1zmf7Tt460HynqbVbpfvMwkisRnu305Z1tGktdbWpZbACbQxyEXcPfbzTM2yzIN0zQDpOUADlKhTLUCgCqyuDPwECFtclI9IdZgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Architecture diagram&quot;
        title=&quot;Architecture diagram&quot;
        src=&quot;/static/92608eaf26aa6f29701401746d598a32/ff2de/architecture-diagram.png&quot;
        srcset=&quot;/static/92608eaf26aa6f29701401746d598a32/8890b/architecture-diagram.png 293w,
/static/92608eaf26aa6f29701401746d598a32/1f316/architecture-diagram.png 585w,
/static/92608eaf26aa6f29701401746d598a32/ff2de/architecture-diagram.png 790w&quot;
        sizes=&quot;(max-width: 790px) 100vw, 790px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The main architecture bit is the one which is part of the project. There are two parts, the &lt;a href=&quot;https://wherestreaming.online&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;website&lt;/a&gt; and an android app which is under development at the moment. Both of them depend on a dotnet core class library which handles all the logic. It calls into &lt;a href=&quot;https://www.themoviedb.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Tmdb&lt;/a&gt; API and &lt;a href=&quot;https://www.utelly.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;UTelly&lt;/a&gt; API via &lt;a href=&quot;https://rapidapi.com/marketplace&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;RapidAPI&lt;/a&gt;. Tmdb is free and allows unlimited calls, so that is good for this project. But RapidApi only allows 1000 free calls a month.&lt;/p&gt;
&lt;p&gt;To reduce operational costs, I used &lt;a href=&quot;https://azure.microsoft.com/en-gb/services/cache&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Cache for Redis&lt;/a&gt;. The call to Tmdb has a TTL of 1 day (to avoid redundant calls) and the Rapid API one has a TTL of 30 days. So we can do 1000 unique free calls, anything more than that will have a cost.&lt;/p&gt;
&lt;h1&gt;CI CD&lt;/h1&gt;
&lt;p&gt;In my opinion, a good, reliable CI-CD is a must for robust software development. Azure DevOps (ADOS from here on) provides a very good suite of tools for this. I like GitHub actions, but as most of my private projects are currently on ADOS, it is easier for me to leverage that.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6e7b1f762ee3e03fcd5bd98c3a0aede5/44079/ci-cd.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 29.692832764505116%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAA7CAAAOwgEVKEqAAAABPklEQVQY012QMU/CQBiG+2/8G/4QEyc3EwdnN03cNAwOLiYGB03c1MUYNxYxxGCMIBKiUqC9cqWFo9C7e0wLiPpOd8+Xe7/3PQdAfzRJ68/ksgZjLAt1+ympnt2XFNQoRngd/J6LUqMf7qTVB4a7W4wOd0hfKuj5oFIP2St22TjweGtPcpYtqvUVX8MJPRFTeXWp1lzCoaIqFIMkxRmfHTFYW2W0v8348oTpPMtduc/Keo3NQjsnU23yiDetiLKveKqHFG8/uSp1kInloiFpDyc4WY3x+THqtLDsY2flrksB948iP2tjsdbi+T4iCPgtz/MIpMQag6OShDCKCOOY7Ovs3ExKSddt4XsuQgQYY9Da8N5o4PV6JMmELHSqDUIIAiFQSuFkrsbo/EFmtjCMoohms4WU4Ty0/TNfsP/6BpKewy0KiMQuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;CI CD&quot;
        title=&quot;CI CD&quot;
        src=&quot;/static/6e7b1f762ee3e03fcd5bd98c3a0aede5/463a7/ci-cd.png&quot;
        srcset=&quot;/static/6e7b1f762ee3e03fcd5bd98c3a0aede5/8890b/ci-cd.png 293w,
/static/6e7b1f762ee3e03fcd5bd98c3a0aede5/1f316/ci-cd.png 585w,
/static/6e7b1f762ee3e03fcd5bd98c3a0aede5/463a7/ci-cd.png 1170w,
/static/6e7b1f762ee3e03fcd5bd98c3a0aede5/44079/ci-cd.png 1211w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The whole continuous integration process of build, test and the following continuous deployment is automated. I am in the process of adding one manual approval step before the staging-production slot gets swapped.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;In conclusion, if you are curious about where your favourite show is streaming, do check it out at &lt;a href=&quot;https://wherestreaming.online&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;https://wherestreaming.online&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Overall doing this project during the holidays was both fun as well interesting. Learnt many tips and tricks which I will share in follow up posts. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Static analysis of Azure resource configuration]]></title><description><![CDATA[Introduction Static code analysis of code has been there for quite a while now and it is very useful now in case of configuration as code…]]></description><link>https://rubberduckdev.com/terraform-static-analysis/</link><guid isPermaLink="false">https://rubberduckdev.com/terraform-static-analysis/</guid><pubDate>Thu, 24 Dec 2020 08:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Static code analysis of code has been there for quite a while now and it is very useful now in case of configuration as code scenario as well. Often, we make mistakes in our configuration code (HCL or not), but if you are using &lt;a href=&quot;https://www.terraform.io/docs/configuration/index.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Terraform HCL&lt;/a&gt; then &lt;a href=&quot;https://github.com/tfsec/tfsec&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;TFSec&lt;/a&gt; is a good choice. When I tried, I was up and running within 5 to 10 minutes, soon followed by CI with Github actions. I like docker in general, so took the docker approach, which hopefully makes it more usable across environments.&lt;/p&gt;
&lt;h1&gt;Festive Tech Calendar 2020&lt;/h1&gt;
&lt;p&gt;This demo is designed as my contribution for &lt;a href=&quot;https://festivetechcalendar.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Festive Tech Calendar 2020&lt;/a&gt;, definitely check out the website for some many other great topics. Happy Christmas.&lt;/p&gt;
&lt;p&gt;This video is part of the festive tech calendar 2020.&lt;/p&gt;
&lt;p&gt;
          &lt;div
            class=&quot;gatsby-resp-iframe-wrapper&quot;
            style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden;margin-bottom: 1rem&quot;
          &gt;
            &lt;div class=&quot;embedVideo-container&quot;&gt;
            &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/PbS_lQQ1dnE?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0;
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
          &quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
        &lt;/div&gt;
          &lt;/div&gt;
          &lt;/p&gt;
&lt;h1&gt;GitHub&lt;/h1&gt;
&lt;p&gt;All the code shown in the video is available at &lt;a href=&quot;https://github.com/realrubberduckdev/terraform-static-analysis&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;https://github.com/realrubberduckdev/terraform-static-analysis&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saves you some time if you are trying this out. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Xamarin - Complying with AdMob policies]]></title><description><![CDATA[Introduction Xamarin has made monetization of mobile cross-platform apps very easy. Mainly with nuget packages such as Xamarin…]]></description><link>https://rubberduckdev.com/xamarin-admob/</link><guid isPermaLink="false">https://rubberduckdev.com/xamarin-admob/</guid><pubDate>Sat, 31 Oct 2020 18:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Xamarin has made monetization of mobile cross-platform apps very easy. Mainly with nuget packages such as &lt;a href=&quot;https://www.nuget.org/packages/Xamarin.GooglePlayServices.Ads.Lite/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Xamarin.GooglePlayServices.Ads.Lite&lt;/a&gt;, adding &lt;a href=&quot;https://support.google.com/admob/answer/6128877&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;admob banner ads&lt;/a&gt; has become a breeze.&lt;/p&gt;
&lt;h1&gt;The Xamarin Show&lt;/h1&gt;
&lt;p&gt;James Montemagno has a great video on the Xamarin show explaining how to add banner ads on android apps.&lt;/p&gt;
&lt;p&gt;
          &lt;div
            class=&quot;gatsby-resp-iframe-wrapper&quot;
            style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden;margin-bottom: 1rem&quot;
          &gt;
            &lt;div class=&quot;embedVideo-container&quot;&gt;
            &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/6teJvSCg6UA?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0;
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
          &quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
        &lt;/div&gt;
          &lt;/div&gt;
          &lt;/p&gt;
&lt;h1&gt;SimpleJigsaw&lt;/h1&gt;
&lt;p&gt;With help and inspiration from James Montemagno, two of my close friends &lt;a href=&quot;https://sanjeebsks204.wixsite.com/sanjeeb-swain&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Sanjeeb Swain&lt;/a&gt; &amp;#x26; &lt;a href=&quot;https://www.zigzagrainbow.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Suryakiran Maruvada&lt;/a&gt; and I set out to design our first android app.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.rubberduckdev.simplejigsaw&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 707px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 125.59726962457339%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAE0klEQVQ4y6WVe1BVVRTGz3+VUzlj5kyj/VOTpsZID00hTR1Tm/FZogLhI8QLAhcQrlzA4IIPSgFJATEQA0kdpxKdMKMxqfDRDD4yEiwM5PK43Bf3/b7315zjQGaYPdbMd9ba+5xZ+1tr7/0dwWky82nDdT4+1UzFyWsc/vJnbIYBTCYTmj4NOq0Ovd6AWq3GarEiWiAQuC8EbZ+OkbFnEOQtqPbGMUeWRUv7AHaHjs7eDnp0avr0PWgNGoxm/VDCu20oIQGEvm49Iao6QiobqGlM5s10BS2t3did/RiMBrw+Dz6/D3/AL/nhGN5tQnuXlXW527lychojT9UjJJTT0dqO3+fDaBjA7XL/hc3fxYJZayBuVz1h6WUs3rKPqKwyNLd7MNmMaPS96E06zDYTPp9vCF6vV4LfH8DmtFDVqKLodDxtvc0IeP7M4L9Yn62dDtN1bG4Tgt3hICcnm8T4WBRpKaRuTiJdsZkjtbVkKRVsVaah2qokQ5FCcVEBFQfKSYjbKI0zt2wmJXETNQerKd+7H61Gi6DT6QgKnc/o8dMZ/Xwoo8bP4KnJIYSvjeaxpycxYlwQD48LQhj1LBNfmcXy8LWMGDuZJyfM4IkJ03lo7AssWBZG6PyltLX/hqDVajl7+Qbfq41cUNu51O3istpMRfURvm3T8FWbnm/azFzs8nLm4k8crz/L+V4HX3dY+K7TydV+H8fq6qlvakY8A4JG08eJph/5/NI1cvcUc7yhkR6jmQMH91F4KJHdR2XkV8azpzqZvIJkPjlWReluGQU56zlRs41T5z9j1+Eqjn5xDoc3cKfkhOxCXl+4lOCXgpm7YAG5+YXsKMggQjYOecKLyBVTiN8YzIaMV9kiX8PsKY+j2r6SktQQyo7kIq8so7RoJybzAIJer2funFlMnfYyK1e9RdiKJcyc9RqpGTL2f7iEQzkx1OxIojIvkuLKCOYtWsyY0BmoLpwmVTaXD/MjUZZ8gEIhQ6vtRejv7ydTKUeeIidVqSBPlU5kRBhJyhhKC5ZwrnYHDceLaDysIHvrbDZVlbP6ZBUpxRtQKpahkk2jpDADWVoq6u5usYcakuXriYhaQeS6d5i3cB4jHnmUd2NXsSv7DQ5ti6GuNInVC4N5ZuwY4ir2sbTuI9Y3HqWueg21B9ew8f0M9mbGou3pFHuoZc6GJCaFJzIlKplJ4Qk8tzya6JRU5sck8LYiizBlLhPDopm6fB1R23YSlL2bmfklrFTKWaRMY8WWTJLz3qNXr0fweNxc7VBz5baGK7f7uNaloaVXx83OLi62d3PhVi/nf1XT3KXlRreGG7c6udTawQ+tnTT90kfTzR5ab3XS3N6Fze1FkO6O14PXacfvduFx2vE6HbicDskH3E78bicBjwu/+J3bTcDrBp8XvC4J4tyQ2ogqYbPbsVitEswWy53YYpFiq82GxWJFvKI+vx+vKA7DQHwnCaz4cLlc2O12HA6HFIt+OBuUqLv9vRImlSzKvXgFDQaD5MWjJB54cWw0GrHZbFhF9mazFA+ORRIixNjv9/9Rsqhtbrcbj8cjQYxFpoNz/9SGShZXENmIpQ6uNOw/4wEYYihuwMDAgAQxsajK9+vRg0xKOFjmYIn3Nv/fmLQpIqvBTRAb/38S/g6jsojAQlbYNgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;simple jigsaw&quot;
        title=&quot;simple jigsaw&quot;
        src=&quot;/static/5d06feb6dffc78c47403275185734c5e/0df09/simple-jigsaw.png&quot;
        srcset=&quot;/static/5d06feb6dffc78c47403275185734c5e/8890b/simple-jigsaw.png 293w,
/static/5d06feb6dffc78c47403275185734c5e/1f316/simple-jigsaw.png 585w,
/static/5d06feb6dffc78c47403275185734c5e/0df09/simple-jigsaw.png 707w&quot;
        sizes=&quot;(max-width: 707px) 100vw, 707px&quot;
      /&gt;
    &lt;/span&gt;&lt;/a&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;h1&gt;Monetizing SimpleJigsaw&lt;/h1&gt;
&lt;p&gt;Following the video above and a few other tutorials, we managed to add ads in our app.&lt;/p&gt;
&lt;p&gt;Obviously, we got AdMob id details with a banner unit:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 726px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.40273037542661%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACKklEQVQ4y41TW5LVIBTMKl2N63AXLsIV+Dd++DFakxtIIOEZHgHSFidXvVbNqKnqnAMFTZ/X4FLAN83hjIXSGtZarOsKpdQFrXGeJ84Td9vexFEKBh8DnsZnzIyDcw7GOW63EWxitF6WGbvfse87UkoI+UTMJ0I6//DTAdTaMKSUYW1Gygk5d5vpYk4Zx3HQXggBznuye6wIsWCPBf5uO0KqdH4IIcI5C+scrP0NYwy0NpdvLfYQ749FevA1hBi7woSXccQ0MUzTBMYYpJRY5hmMTVilwDxzhLDjX19tDUMPSRkDIQXWVWLbNpLe2okG4KhAPYFcgJQLYgikpJRCJFehTvJLrRhq7bFniGWh6nbSZe4FmsFmDS4sZmGwrB7a7FBqw6YUFak/THk+Mlpr6FxDKZUcbTTlTMoN66awCAkpV1Lcw++h96I8opN63+FJMVW5/7TWeBknTGwDWxyYcOCrBxceE+MQQtAlZx200nTeaANjLPl97xdhj5tI5XdIwakAM58wji+43W4UOp9nUprz1UavVbjVeg+5Xv2jlCD51gU4F6htOvrkkCJjSGUPtZM+ggh/5vAoFdbtl3x3wOyAj6C+9M7BOUeEvR+7jTG+3TadEK0gxAblG1ZbsdpCc/m3r6uh+W0XrvVJeRw+P3t8+uIg1A5tAzYToF0k3/mIPkl9SsIj4hsIEcP7jxLvPnB8nQLmLWEUCdOacJMJymXk40BMB1L+P/wARUCGbgr6ZVsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;admob ids&quot;
        title=&quot;admob ids&quot;
        src=&quot;/static/c6a965890238a1e2de84c8cd845be7e4/5afa3/admob-ids.png&quot;
        srcset=&quot;/static/c6a965890238a1e2de84c8cd845be7e4/8890b/admob-ids.png 293w,
/static/c6a965890238a1e2de84c8cd845be7e4/1f316/admob-ids.png 585w,
/static/c6a965890238a1e2de84c8cd845be7e4/5afa3/admob-ids.png 726w&quot;
        sizes=&quot;(max-width: 726px) 100vw, 726px&quot;
      /&gt;
    &lt;/span&gt;
&lt;br/&gt;
And then added these along with the ad control view, ad view renderer and other updates in a commit. This is available on &lt;a href=&quot;https://github.com/realrubberduckdev/xamarin-admob-demo/commit/e3f2fa1cd9f564c522f2e56522ea8f285e24e782&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;github for reference&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;AdMob Policy Issue&lt;/h1&gt;
&lt;p&gt;Although the commit above works perfectly on the local machine and for a day or so did work in the published app, there was an issue. Within a few days, I received an email from Google.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 889px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a89b0c2bdbbaf316e9c6c274c7c0908b/ef9d1/policy-violation-email.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 79.86348122866895%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAC4ElEQVQ4y5WU328UVRTH9+8gMQQDL6CiUiSFYGOt1CrQEKTG+IA/nlSCISSg6ROJz4Qn+SG0gNBQIC0+8II14psPJEYsW8p2W2Hb2dmdOzs7v2c6Pz7mXrplS9DISb45555zvt+5k3vPLeR5joQ0y7LQNI1qtUq9LqgpGAizQb1eQ9d1VV9YWFA9jyoV6oahuC2dQp5l5ElCnqY4loWuadSqVRqWj2hIBDhOQBKFxEFA4HmEvkekYpc48MnTZFmjQJvFQAgE8oustAxYBJIlHy/lkqf6CuHUPazxUZo3b2CMjVK7eknBHB9VeevGVayfrqm1GLuCuD6iYFwfwRy7gjl+jebNcdXj3fmdQv3UCSZffoH7219h5v03Ke/pofxBL6X+bmY/7mdmTw+lnV3M9HcrlPf1MbvvXcp7d1Ae6GNmVxdT216i+PqLVL45SME4f5r7Xa8p0sOvPkE7dhTtu0Hmv/2ahcFDzB85QOXwF8wPHuLhl/uZ+3SAvz//kLn9e5n7bIDZj3Yp7vTbmxWvUPv+OJPrV1Hs3MB0dwelvm1M93ZSem870z1v8KB3Kw/e2UJp91tqPfnqGqY61lHsWMu9TWspbl5HsXM9xY2rqRw5QMH/4w5i6CTmyDDmxR8QF88oL9H48azyxoUziPOnETJ/eRjz8hDi0pDyLci6fXti5SlLS5fQOtWU57Mn9zBJyBYXqes69WqVmqahzVcwdF3lydI2ZEtIVa3FV/ewNSXSZ1mGbds0m01c18XzPBXbtoPnR7heiOcF+L6vamEYKk67xjMFTdNUgpIQyokII5wgww0z/DBR+SAI/p+g4zhqpqWw2q3t0Gw6yzuXNdkj4zRNaX8LnikoCUIIDEMgDIPIFQqhK4h9E8e2cFxXibZ296+CT+LHJxYu5pz9Nebc7Zjh32JOTcTo9sop/09B+QstZFlKGKf88lfEz3cjJu5G3PozouEm5FlKkiTLve2C/wAYL26gUU5LPAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;policy-violation-email&quot;
        title=&quot;policy-violation-email&quot;
        src=&quot;/static/a89b0c2bdbbaf316e9c6c274c7c0908b/ef9d1/policy-violation-email.png&quot;
        srcset=&quot;/static/a89b0c2bdbbaf316e9c6c274c7c0908b/8890b/policy-violation-email.png 293w,
/static/a89b0c2bdbbaf316e9c6c274c7c0908b/1f316/policy-violation-email.png 585w,
/static/a89b0c2bdbbaf316e9c6c274c7c0908b/ef9d1/policy-violation-email.png 889w&quot;
        sizes=&quot;(max-width: 889px) 100vw, 889px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;On visiting the policy centre, we found the following message:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Modified ad code: Resizing Ad Frames
MODIFIED ADS: Publishers are not permitted to alter the behaviour of Google ads in any way. This includes resizing ad frames to cut off parts of ads or hiding the Ads by Google moniker.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Quite baffling for untrained and hobbyist Xamarin developers. Because violating policy was never the intention. Then we started following the policies in more detail at &lt;a href=&quot;https://support.google.com/admob/answer/6128543?hl=en&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Google AdMob help&lt;/a&gt;. Finally, we understood that the ad banner under no circumstances should be clipped/resized and that can be avoided by ensuring it gets the height it gets.&lt;/p&gt;
&lt;p&gt;To achieve this we added &lt;code class=&quot;language-text&quot;&gt;IsClippedToBounds=&quot;False&quot;&lt;/code&gt; to the grid view containing the ad banner.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;Grid IsClippedToBounds=&quot;False&quot;&gt;
    &amp;lt;Grid.ColumnDefinitions&gt;
        &amp;lt;ColumnDefinition Width=&quot;*&quot; /&gt;
    &amp;lt;/Grid.ColumnDefinitions&gt;
    &amp;lt;Grid.RowDefinitions&gt;
        &amp;lt;RowDefinition Height=&quot;Auto&quot; /&gt;
    &amp;lt;/Grid.RowDefinitions&gt;
    &amp;lt;local:AdControlView x:Name=&quot;adControlView&quot; Grid.Row=&quot;0&quot;/&gt;
&amp;lt;/Grid&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in the next bit was ensure it gets the size it wants. The smart banner sizing is described at &lt;a href=&quot;https://developers.google.com/admob/android/banner#banner_sizes&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Google AdMob help&lt;/a&gt;.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 885px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.36860068259386%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABnElEQVQozz1QiY6rMAzk/7/wbbcXbSEQch+QEGBWMdWzZCW2Zzy2G2sdpklASgVtLKRSUEpDKg2tDfK6Yi0F61ron3I+43LGOZ9e66VsaHo24nZv8Xr3EMpikgZSWbBR4Hp7omMcfFLgQuPRfvDze8fAJcXtq8Pl+sDz9aGcMg5NncY5j23fSaGUgn3fEWOEEIKmOI6DcsZY2oZzjpwzbcRGjnlJVN+2HY33HiFEVKvE6tXqGlJK1HpKifJVpJ5BSEnCIc7gk8AkJZ2iWmOtJSAOkEr1Sq5NlFLUVGtNYO/8KVAn2jaEGKlhvXtKmXiNsQ5xnomwHwe274QprxBSYRISeS2Uq9jwxVYL84IPG6CdP/n7jsaOI0TfQw8DZN/DjCO2GDC7CDY4TNyhxIAtRljOCTN1HZK18FIiao3sHIr3qLM04nLBcLmA/fvBcH0gMI7QM6j2g/fvG/zJ4PoRcRhh2hbidsd0vWJmDLp9gX35S9dhrzecQ4DXGt5YaLOQW+PhlILXCtYEKJMQw4I9Z2w5n29K//8Ul0I3/AO5mLHIHC6yvgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;banner-sizes&quot;
        title=&quot;banner-sizes&quot;
        src=&quot;/static/5ed4eff6106f74082ef8447074f0e987/25849/banner-sizes.png&quot;
        srcset=&quot;/static/5ed4eff6106f74082ef8447074f0e987/8890b/banner-sizes.png 293w,
/static/5ed4eff6106f74082ef8447074f0e987/1f316/banner-sizes.png 585w,
/static/5ed4eff6106f74082ef8447074f0e987/25849/banner-sizes.png 885w&quot;
        sizes=&quot;(max-width: 885px) 100vw, 885px&quot;
      /&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;    class AdViewRenderer : ViewRenderer
    {
        private Context _context;

        public AdViewRenderer(Context context) : base(context)
        {
            _context = context;
        }

        protected override void OnElementChanged(ElementChangedEventArgs&amp;lt;View&gt; e)
        {
            base.OnElementChanged(e);
            if (Control == null)
            {
                var ad = new AdView(_context);
                ad.AdSize = AdSize.SmartBanner;
                ad.AdUnitId = SimpleJigsawConstants.adMobBannerUnitId;
                var requestbuilder = new AdRequest.Builder();
                ad.LoadAd(requestbuilder.Build());
                e.NewElement.HeightRequest = GetSmartBannerDpHeight();

                SetNativeControl(ad);
            }
        }

        private int GetSmartBannerDpHeight()
        {
            var dpHeight = Resources.DisplayMetrics.HeightPixels / Resources.DisplayMetrics.Density;

            if (dpHeight &amp;lt;= 400) return 32;
            if (dpHeight &gt; 400 &amp;amp;&amp;amp; dpHeight &amp;lt;= 720) return 50;
            return 90;
        }
    }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In order to obtain the correct size of banner, we introduced a new function &lt;code class=&quot;language-text&quot;&gt;GetSmartBannerDpHeight()&lt;/code&gt; in the &lt;code class=&quot;language-text&quot;&gt;AdViewRenderer&lt;/code&gt; class. This would automatically get the correct height as that is dynamic.&lt;/p&gt;
&lt;p&gt;One more thing to note is that in the &lt;code class=&quot;language-text&quot;&gt;XAML&lt;/code&gt; file, we started naming the &lt;code class=&quot;language-text&quot;&gt;adControlView&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;local:AdControlView x:Name=&quot;adControlView&quot; Grid.Row=&quot;0&quot;/&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is because we learnt from a &lt;a href=&quot;https://www.b4x.com/android/forum/threads/solved-google-admob-restricted-ad-serving-modified-ad-code-resizing-ad-frames.112884/post-704612&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;community post&lt;/a&gt; that if an ad banner is active, the user must not be restricted to click on it, else it will cause a policy violation. So we use that name in a wrapper function to ensure the banner gets hidden if we are to show an alert.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;private async Task&amp;lt;bool&gt; PerformCustomDisplayAlert(string title, string message, string accept, string cancel)
{
    // https://www.b4x.com/android/forum/threads/solved-google-admob-restricted-ad-serving-modified-ad-code-resizing-ad-frames.112884/
    // Hide ad before showing alert, else Google thinks we are blocking user access to their ads.
    adControlView.IsVisible = false;
    var res = await DisplayAlert(title, message, accept, cancel);
    adControlView.IsVisible = true;
    return res;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And since these changes, our ad seems to work fine on our app. The full &lt;a href=&quot;https://github.com/realrubberduckdev/xamarin-admob-demo&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;demo code is available on github&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;As you may have already got, this was a learning experience. And this development is as a hobby, we welcome any help and advise you can provide. Definitely try out the app if you can and provide feedback.&lt;/p&gt;
&lt;p&gt;Hope this was useful and saves you some time if you are trying this out. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on Twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Terraform - Raise an error]]></title><description><![CDATA[Introduction The ability to conditionally raise errors is very useful if we are introducing logic and rules in our deployment. Say for…]]></description><link>https://rubberduckdev.com/tf-raise-error/</link><guid isPermaLink="false">https://rubberduckdev.com/tf-raise-error/</guid><pubDate>Thu, 17 Sep 2020 08:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;The ability to conditionally raise errors is very useful if we are introducing logic and rules in our deployment. Say for example we need to validate input then we can use the newly introduced &lt;a href=&quot;https://www.hashicorp.com/blog/custom-variable-validation-in-terraform-0-13&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;variable validation feature in Terraform 0.13&lt;/a&gt;. But there could be a specific case where you’d want to raise an error or you’ve used a workaround for this validation in 0.11 which no longer works after the upgrade. More on this in a bit, and that is the case I hit recently.&lt;/p&gt;
&lt;h1&gt;Workaround&lt;/h1&gt;
&lt;p&gt;In Terraform 0.11, there was no ability to validate input variables. So we used a workaround with &lt;code class=&quot;language-text&quot;&gt;null_resource&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;resource &quot;null_resource&quot; &quot;throw_error&quot; {
      count = 1
      &quot;An error has occured.&quot; = true
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But unfortunately and possibly rightly so, HCL became stricter from 0.12 onwards and would not allow incorrect syntax such as &lt;code class=&quot;language-text&quot;&gt;&quot;string&quot; = true&lt;/code&gt; and fail. There is a &lt;a href=&quot;https://stackoverflow.com/questions/56042077/terraform-v0-11-xx-null-resource-not-always-works-as-assertion&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;stackoverflow question&lt;/a&gt; regarding this. Along with the StackOverflow question, there are lots of requests and suggested workarounds on GitHub such as &lt;a href=&quot;https://github.com/hashicorp/terraform/issues/15469&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Ability to raise an error&lt;/a&gt; &amp;#x26; &lt;a href=&quot;https://github.com/hashicorp/terraform/issues/17977&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;conditionally raise a configuration error&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So the workaround we went for in my my team, &lt;a href=&quot;https://github.com/hashicorp/terraform/issues/17977#issuecomment-693500261&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;suggested on github&lt;/a&gt; is:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;data &quot;external&quot; &quot;throw_error&quot; {
    count = 1 # 0 means no error is thrown, else throw error
    program = [&quot;powershell.exe&quot;, &quot;throw &apos;An error has ocurred.&apos;&quot;]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Explanation&lt;/h1&gt;
&lt;p&gt;The workaround uses &lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data_source&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;external data source&lt;/a&gt; which allows an external program to act as a data source. So essentially it is a hack, saying &lt;code class=&quot;language-text&quot;&gt;PowerShell&lt;/code&gt; will be our data source but instead, it throws an error. This happens conditionally only if we set the count to greater than 0. If set to 0, this &lt;code class=&quot;language-text&quot;&gt;data&lt;/code&gt; block is not executed.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saves you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why is my PowerShell function returning two values?]]></title><description><![CDATA[Introduction This post is regarding a small PowerShell function that I was writing. It started returning two values at the same time and it…]]></description><link>https://rubberduckdev.com/ps-func-returning-2-values/</link><guid isPermaLink="false">https://rubberduckdev.com/ps-func-returning-2-values/</guid><pubDate>Wed, 05 Aug 2020 17:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This post is regarding a small &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/scripting/overview?view=powershell-7&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;PowerShell&lt;/a&gt; function that I was writing. It started returning two values at the same time and it took me a while to figure out what was happening. Hence this post and hope it saves someone else some time.&lt;/p&gt;
&lt;h1&gt;The function&lt;/h1&gt;
&lt;p&gt;The objective of the function we are writing is to check if an item is present in a JSON array.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
    [
        {
            &quot;displayName&quot;: &quot;Apple&quot;,
            &quot;description&quot;: &quot;Fruit&quot;
        },
        {
            &quot;displayName&quot;: &quot;Potato&quot;,
            &quot;description&quot;: &quot;Vegetable&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our JSON array is a simple &lt;code class=&quot;language-text&quot;&gt;displayName&lt;/code&gt; &amp;#x26; &lt;code class=&quot;language-text&quot;&gt;description&lt;/code&gt; pair elements. And we need an &lt;code class=&quot;language-text&quot;&gt;ItemExists&lt;/code&gt; function in PowerShell returning boolean value, &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt; if the item exists and &lt;code class=&quot;language-text&quot;&gt;false&lt;/code&gt; if it doesn’t.&lt;/p&gt;
&lt;p&gt;Here is a script with the function:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;function ItemExists {
    param (
        [PSCustomObject]$jsonData,
        [string]$itemToLookFor
    )
    $jsonData | Select-Object -Property displayName | ForEach-Object {
        if ($itemToLookFor -eq $_.displayName) {
            return $true
        }
    }
    return $false
}

$json = @&quot;
    [
    {
        &quot;displayName&quot;: &quot;Apple&quot;,
        &quot;description&quot;: &quot;Fruit&quot;
    },
    {
        &quot;displayName&quot;: &quot;Potato&quot;,
        &quot;description&quot;: &quot;Vegetable&quot;
    }
    ]
&quot;@ | ConvertFrom-Json

$exists = ItemExists -jsonData $json -itemToLookFor &quot;Apple&quot;
Write-Host exists=$exists&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, we are looking for string &lt;code class=&quot;language-text&quot;&gt;Apple&lt;/code&gt; and it exists, so it should return &lt;code class=&quot;language-text&quot;&gt;true&lt;/code&gt;. But the output is as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;exists=True False&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Is the function returning two values? And if yes how and if no then why is this happening!&lt;/p&gt;
&lt;h1&gt;Explanation&lt;/h1&gt;
&lt;p&gt;The point of interest here is &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-7#description&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ForEach-Object&lt;/code&gt;&lt;/a&gt;.  Beginning with PowerShell 7.0, it runs each script block in parallel. The first &lt;code class=&quot;language-text&quot;&gt;return&lt;/code&gt; statement is within the scope of the &lt;code class=&quot;language-text&quot;&gt;ForEach-Object&lt;/code&gt; and the second one is outside of the scope. Meaning the first &lt;code class=&quot;language-text&quot;&gt;return&lt;/code&gt; is returning from &lt;code class=&quot;language-text&quot;&gt;ForEach-Object&lt;/code&gt; which is in a separate runspace. This then leads to the next &lt;code class=&quot;language-text&quot;&gt;return&lt;/code&gt; statement. So the function is returning two values, due to the loop running in separate &lt;a href=&quot;https://devblogs.microsoft.com/scripting/beginning-use-of-powershell-runspaces-part-1/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;runspaces&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Rewriting the function&lt;/h1&gt;
&lt;p&gt;How do we get the function to work then?
Let’s rewrite the function without &lt;code class=&quot;language-text&quot;&gt;ForEach-Object&lt;/code&gt;, rather we can use &lt;code class=&quot;language-text&quot;&gt;foreach&lt;/code&gt; loop, as follows.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;function ItemExists {
    param (
        [PSCustomObject]$jsonData,
        [string]$itemToLookFor
    )
    $items = $jsonData | Select-Object -Property displayName
    foreach ($item in $items) {
        if ($itemToLookFor -eq $item.displayName) {
            return $true
        }
    }
    return $false
}

$json = @&quot;
    [
    {
        &quot;displayName&quot;: &quot;Apple&quot;,
        &quot;description&quot;: &quot;Fruit&quot;
    },
    {
        &quot;displayName&quot;: &quot;Potato&quot;,
        &quot;description&quot;: &quot;Vegetable&quot;
    }
    ]
&quot;@ | ConvertFrom-Json

$exists = ItemExists -jsonData $json -itemToLookFor &quot;Apple&quot;
Write-Host exists=$exists&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This time the loop doesn’t run in separate runspaces and hence the output is:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;exists=True&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is exactly the value we wanted.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful and saves you some time. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Finding out IPs of Azure function]]></title><description><![CDATA[Introduction The IP address of an Azure function can be very useful, especially if you are using IP based network restrictions or any kind…]]></description><link>https://rubberduckdev.com/funapp-ip-resource-graph/</link><guid isPermaLink="false">https://rubberduckdev.com/funapp-ip-resource-graph/</guid><pubDate>Sun, 02 Aug 2020 15:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;The IP address of an Azure function can be very useful, especially if you are using &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/app-service-ip-restrictions&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;IP based network restrictions&lt;/a&gt; or any kind of firewall to block requests from unknown IPs. Azure gives us a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/ip-addresses#function-app-inbound-ip-address&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;single inbound IP&lt;/a&gt; address to the function, but a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/ip-addresses#find-outbound-ip-addresses&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;range of possible outbound&lt;/a&gt; ones. This is so that Azure can do its internal load balancing. The issue you might hit is that these IPs can all change.&lt;/p&gt;
&lt;h1&gt;When can IP change&lt;/h1&gt;
&lt;p&gt;There are specific cases when IPs can change. Again these are listed in the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/ip-addresses#inbound-ip-address-changes&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;documentation&lt;/a&gt;. Both the inbound and outbound IP addresses can change. So how do we figure out, on the fly, what are the IPs of our Azure function?&lt;/p&gt;
&lt;h1&gt;Resource graph&lt;/h1&gt;
&lt;p&gt;This is when &lt;a href=&quot;https://azure.microsoft.com/en-gb/features/resource-graph/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Resource Graph&lt;/a&gt; becomes super useful. The resource graph is a powerful management tool to query, explore and analyse your cloud resources at scale. We can see all resources, their properties etc using &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Kusto query language (KQL)&lt;/a&gt; queries.&lt;/p&gt;
&lt;h2&gt;Using resource graph&lt;/h2&gt;
&lt;p&gt;Steps to finding out the IPs of our function are as follows. Note that we have a function app named &lt;code class=&quot;language-text&quot;&gt;blog-test-funap&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to &lt;code class=&quot;language-text&quot;&gt;Azure Resource Graph Explorer&lt;/code&gt; on your Azure portal&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter query&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Resources | where name =~ &apos;blog-test-funap&apos; and type =~ &apos;microsoft.web/sites&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4ceaa3807a3c9663ef0db6fd2ce45b82/52ab3/resource-graph-query.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35.153583617747444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA+UlEQVQoz4WQyW6EMBBEDQf2HRtGgG3AwIkbB/7/yyrqzkw0UpTJ4al6scvtFvPisCwLcxwHhmFAXddomuZJ+xPXRF1/ROzbBmst+r7HOE7QxkKqDl3XQzEPyO4BpTrIViLP84+I+75xnieUUpBSYl0d3LZj3XZszsGtK+cUE3qa+BfjOLK+Mw4DxHVdcM6hLEue0hoDrTVjjOHpqWasZSif5/lPRPPcS1VV6JRiE62/TelVmlpJyfo/LQQZEWRaFAXiOEGcZEjTDGEYQgjBeJ73KyYlfN9nDYIA4jXdy5AWm2UZ18iQapRTn3pRFCFNU778fpb6SZLgC1X+rTzHy0AWAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Resource graph query&quot;
        title=&quot;Resource graph query&quot;
        src=&quot;/static/4ceaa3807a3c9663ef0db6fd2ce45b82/463a7/resource-graph-query.png&quot;
        srcset=&quot;/static/4ceaa3807a3c9663ef0db6fd2ce45b82/8890b/resource-graph-query.png 293w,
/static/4ceaa3807a3c9663ef0db6fd2ce45b82/1f316/resource-graph-query.png 585w,
/static/4ceaa3807a3c9663ef0db6fd2ce45b82/463a7/resource-graph-query.png 1170w,
/static/4ceaa3807a3c9663ef0db6fd2ce45b82/52ab3/resource-graph-query.png 1564w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice the &lt;code class=&quot;language-text&quot;&gt;=~&lt;/code&gt; which means, do a case insensitive comparison. And we filter the resources down to &lt;code class=&quot;language-text&quot;&gt;microsoft.web/sites&lt;/code&gt;, which is what function apps are under the hood.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scroll and click on &lt;code class=&quot;language-text&quot;&gt;See details&lt;/code&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0714ca35c86926a9ee19037302de2fb4/22c6c/see-details.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.781569965870304%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABMklEQVQoz52SW4+DIBCF2dTaRtsIKlqwXgroPrj//++d3ZkW95Zsk334AjOQ4ZwZxO12AxFCwOVygVIKZVk+qLY95Z9B98Q4jlzoeu0ZrRumaRro1qKuNe+VlDidTjifz38i1nVFVVXQWsOHAB8W+DAjOAfvPbwPCN7BOYeu62CtRWctr5EY07kgu0VRsEpS2/c9hoEYOP7KNE1PEeRdSsm26AVj7yoopnzsD+1lUWw5Xin3iKl/5FTEBPk3xqKsarRty/1KkgTH45HXLMuQ5zkOhwP2+z3HaZry+W6XQEmFPMvuCgmyPfQ0mCvbJ4wxrJYeoDxZiufshu5Yi1dj8TY51DTlqJAUCSH+RSoEXmL8WTD/d8FvbA1Wij/3siyY5/kX9Bt+Tn2b/jhimCasH9/mHadg88rbd3UDAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;See detail&quot;
        title=&quot;See detail&quot;
        src=&quot;/static/0714ca35c86926a9ee19037302de2fb4/463a7/see-details.png&quot;
        srcset=&quot;/static/0714ca35c86926a9ee19037302de2fb4/8890b/see-details.png 293w,
/static/0714ca35c86926a9ee19037302de2fb4/1f316/see-details.png 585w,
/static/0714ca35c86926a9ee19037302de2fb4/463a7/see-details.png 1170w,
/static/0714ca35c86926a9ee19037302de2fb4/22c6c/see-details.png 1684w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/li&gt;
&lt;li&gt;See possible IP addresses in &lt;code class=&quot;language-text&quot;&gt;Properties&lt;/code&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 833px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c4c842eca9a1f43da15f9a1a4838d6c5/1591e/properties.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 80.2047781569966%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACAElEQVQ4y52U246iQBCGuZ8LTxyUM4IIDQi4yciIoOJxY7LZuZh9/zf5N9Wiu9mdSca5+NJ00fV3VVd1C8vlEpvNBqIootfrod/v8/Gz3NYPBgOOQIZut8sFJUn6ErIso9PpIMsyCGmaIooiaJoG0zS/hGVZUBQFl8sFQhiGyPOcG0lU1/WHMQyDR3o8HiFQdCRIRlVVueijwhQlpX06nSAwxlAUBZIkQRzH/BwI27bvu39W8Hw+Q/A8j5/h7ceNR1MmwaZpIJAzGQxdv6f7aMrkT0Wp6xqC67rQNZWjDIcY/odyRfmY0WjEW68sSwhUXcs0YJCgLGOoyFBauEMrTE4foakqb/DVanUtymQygWaY8NgMzpTBDWO4QQTb8+GYOmzTwHA0eif6drNWsKqqa4R0jmWa43X/Ha/NAT+2R1zWexyfX3CKGc5JhCYMsGchDlHIv3csvHOIGdaei1/Uh9QetmUhYAmKeo9ltcVzuca3lwpJkiH1PRQJwyIOkQU+5qGPme8haUmnE25njoXzrvkj6IUR5vUB2WqLvNohr/eIkwQZm8JxHJiWBd0woFFf/gXNDdPEQFawXK8hUMlJdBIw5OUGc2K1RVpUiMIAs8Dn15LWvXuPabRtOJKENyrKeDy+vhqyDFlReIPekEQRA1G8vkTt+C/cTuufnvBzscBvuR+5GIFQXVYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;See properties&quot;
        title=&quot;See properties&quot;
        src=&quot;/static/c4c842eca9a1f43da15f9a1a4838d6c5/1591e/properties.png&quot;
        srcset=&quot;/static/c4c842eca9a1f43da15f9a1a4838d6c5/8890b/properties.png 293w,
/static/c4c842eca9a1f43da15f9a1a4838d6c5/1f316/properties.png 585w,
/static/c4c842eca9a1f43da15f9a1a4838d6c5/1591e/properties.png 833w&quot;
        sizes=&quot;(max-width: 833px) 100vw, 833px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that there are multiple JSON properties to get full list of IP addresses such as &lt;code class=&quot;language-text&quot;&gt;possibleOutboundIpAddresses&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;possibleInboundIpAddresses&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;outboundIpAddresses&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Azure resource graph gives a lot of power at your fingertips. This post was a demonstration at Azure portal, but we can do the same using &lt;a href=&quot;https://docs.microsoft.com/en-us/cli/azure/ext/resource-graph/graph?view=azure-cli-latest&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;AZ CLI&lt;/a&gt;, &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/governance/resource-graph/first-query-powershell&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Powershell&lt;/a&gt; or &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/governance/resource-graph/first-query-dotnet&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;dotnet sdk&lt;/a&gt; or &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/azure-resourcegraph/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;REST API&lt;/a&gt;. Thereby having full knowledge of our Azure resources with automation. &lt;/p&gt;
&lt;p&gt;Hope this was useful. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Access restrictions in Azure functions]]></title><description><![CDATA[Introduction Secured access to azure functions is very important, especially when your distributed application can have a public-facing…]]></description><link>https://rubberduckdev.com/funapp-testrun-nw-restriction/</link><guid isPermaLink="false">https://rubberduckdev.com/funapp-testrun-nw-restriction/</guid><pubDate>Sun, 02 Aug 2020 12:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Secured access to azure functions is very important, especially when your distributed application can have a public-facing infrastructure. Be it public IP addresses or user interface. &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/role-based-access-control/overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure RBAC&lt;/a&gt; roles provide a great way to control who can trigger your function or what your function can do. On top of that, &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/app-service-ip-restrictions&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;network access restrictions&lt;/a&gt; add the perfect layer of security to avoid unwanted sources to be able to trigger your function.&lt;/p&gt;
&lt;h1&gt;Developer access&lt;/h1&gt;
&lt;p&gt;During the development phase, any access restrictions set up can cause issues for the developers. For example, if we create a sample function and add a restriction rule (check screenshot), then a &lt;code class=&quot;language-text&quot;&gt;Deny all&lt;/code&gt; is automatically added at the end of the restrictions list. The logic being, if we want specific IPs/Vnets etc to be allowed in, then we must want the rest of the sources to be locked out. &lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/62e7bd5705428225de030596bd47afda/0c221/deny-all-rule.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 28.668941979522184%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAwklEQVQY05VQWw7CMAzbDZaka9fnxmuCjfufz6hhlUAgIT6sxElc2e2W64bL9Q43ehARmPkvVE2DiKAr04yUIpgJfd+/HfwCfzHQ3dYN27qilIKc8wfqPKX0Ptur8xFmGGCMwbDXbjqekaeDiprQjaMua4R23ASvnEg+HaaUIWLUfowRzlrta6QYgvJ+56Fy5/RrRAhl9jBmAPEzuv7hEjxO1uJoLW45YwkBJ+cUa43mPWg/bs6Yay+YS4Bokue+PvgAIOOVqzhwy1wAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Deny all rule&quot;
        title=&quot;Deny all rule&quot;
        src=&quot;/static/62e7bd5705428225de030596bd47afda/463a7/deny-all-rule.png&quot;
        srcset=&quot;/static/62e7bd5705428225de030596bd47afda/8890b/deny-all-rule.png 293w,
/static/62e7bd5705428225de030596bd47afda/1f316/deny-all-rule.png 585w,
/static/62e7bd5705428225de030596bd47afda/463a7/deny-all-rule.png 1170w,
/static/62e7bd5705428225de030596bd47afda/0c221/deny-all-rule.png 1685w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;
It makes sense. But the issue is as a developer, you can no longer test run the function on your azure portal.&lt;/p&gt;
&lt;p&gt;As the source of the trigger, the Azure portal is now from your developer machine. So you see the following error: &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;You must have direct network access in order to run your function. Your app may be restricted with Private Endpoints, Access Restrictions or Service Endpoints.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/db21467de319a1f6651e3ced16f0d51b/129e8/cannot-test-run.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.20136518771331%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAA7DAAAOwwHHb6hkAAABc0lEQVQoz0WRS2+bQBRG2bSK3AYDBmweMxjMDBgG7DzkJm5XrdRFU6lN1GX//9841UzkdPHpSleao3O/8XbDTN93VFVFWZZEUUQUrYjjmDhOSJKEIAhYBoGb137Au4XN8i3vF6+7xXWIt61riqJwD4UQ1NuKuq6RVYWQ0iVNU5dVnNLJFX++VLycBc8PGc8POb9OG15OK74eC7zz+RPDMDpI3eyYj7cc5om2bZEWKISb9oJCSKY25+83xe9HwdP9mp/3G37cpTwdA76fGjxZrknTNUvfR8iK3hww04wZBjqtUW2LVoqmaVwleSmp9USYFiz8mA/LhI9BypUfU8gG79JNGIbOZKe0M+67jk4rB7OxFxRliZSCz+dHjocZ1e6Q4nUnygKtFZ4FBUH4BtT9wO3NDcZMjOPIZIwzbWwl24o8y5imCaUUeZ47a2ee52itL8D/htbEAsb9nnHfY/Y9bbOlbWqXLNtgjHGx/doPvQC7ruMfQyXAL/Y3NMoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Cannot test run&quot;
        title=&quot;Cannot test run&quot;
        src=&quot;/static/db21467de319a1f6651e3ced16f0d51b/463a7/cannot-test-run.png&quot;
        srcset=&quot;/static/db21467de319a1f6651e3ced16f0d51b/8890b/cannot-test-run.png 293w,
/static/db21467de319a1f6651e3ced16f0d51b/1f316/cannot-test-run.png 585w,
/static/db21467de319a1f6651e3ced16f0d51b/463a7/cannot-test-run.png 1170w,
/static/db21467de319a1f6651e3ced16f0d51b/129e8/cannot-test-run.png 1691w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Allow list yourself&lt;/h2&gt;
&lt;p&gt;As we know why our request is being blocked, the simplest solution is to allow list ourself.&lt;/p&gt;
&lt;p&gt;Steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get your public ip, I used google
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 573px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dc53317bf5499e707acf2bf2398302b0/aec1a/own-ip.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.84641638225256%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABKElEQVQ4y5VT2W6EMAzk/z+yr7uAgDghIZBjqnEVStXVHpZGsU0YH7G7Ugqcc/DeK47jQK0A/bVWRSn1j97sUn/9DV3OGcuywBijxLQ/lUZG6VALcqnIBQghIMaIlBKsc9j3A9u2wblVM9/3HevqsXqPlDNC2PRuI6SvCzHjq48YJUHEwPug5YpYxLgroYgoGSHW6jcSretPoCb0dUfKGCYDI+68bERUZ5ZEI3+vZFSwbGjDy1PUy52m/3sUslprcbvdME0T5nl+mckzdC0SX5doqb/z80NC/mzMgr7vFeM4YhgGDdKIPwlwlkyS+/1+npzNa4/elbNkjgRHgHqbuU/7d2ZIAoIzx2ydjoroyUfiFl1b8JKQA0lwS7gtBPeaNk/aj0bkEeE3xev+4gQ3138AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Getting own ip&quot;
        title=&quot;Getting own ip&quot;
        src=&quot;/static/dc53317bf5499e707acf2bf2398302b0/aec1a/own-ip.png&quot;
        srcset=&quot;/static/dc53317bf5499e707acf2bf2398302b0/8890b/own-ip.png 293w,
/static/dc53317bf5499e707acf2bf2398302b0/aec1a/own-ip.png 573w&quot;
        sizes=&quot;(max-width: 573px) 100vw, 573px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/li&gt;
&lt;li&gt;Add to network restrictions
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d99f5dd0dc2b4ebf0924797b4b21cb99/c2b06/allow-own-ip.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.78839590443686%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAA7DAAAOwwHHb6hkAAABBklEQVQoz42R4W6EIBCEeYETFhYQEVGrR9LmmvTe/92mAau5/mp/fAmzZCc7GbGXd2zlA8wWSikQ0Z9IpeC9Q7nfUUrBsixtV2sNMc8LxjG2wX+RXQfnAnLeQZKgOoIhhiEDsd8L9n1DCANCCI1hGBoxxuv9axZ6jHnHtD2bSUU3NEReN0zzipQmpJSQc0bf97DWXjjnGsx8zJyDdxaOGUYz2NgfGGKIEcZwy1+vqwtnNO99m5/6/O9aZIecElgrsKYDoyGYDYiOMupCvaK+q1GNV01PXc2MMYc2HuP8APcbJC9QvELqCPHa3nnJ1aaUl34thZTCjRzs/IXw9oROD1D6xM3O+AYDtq8tzUHz2AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Allow list own ip&quot;
        title=&quot;Allow list own ip&quot;
        src=&quot;/static/d99f5dd0dc2b4ebf0924797b4b21cb99/463a7/allow-own-ip.png&quot;
        srcset=&quot;/static/d99f5dd0dc2b4ebf0924797b4b21cb99/8890b/allow-own-ip.png 293w,
/static/d99f5dd0dc2b4ebf0924797b4b21cb99/1f316/allow-own-ip.png 585w,
/static/d99f5dd0dc2b4ebf0924797b4b21cb99/463a7/allow-own-ip.png 1170w,
/static/d99f5dd0dc2b4ebf0924797b4b21cb99/c2b06/allow-own-ip.png 1679w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ensure you use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;CIDR&lt;/a&gt; to allow list only the specific IP and not a range. For security, you do not want more than your IP getting the access to your function.&lt;/p&gt;
&lt;p&gt;If the concept is new, here is a fantastic video explaining how the subnetting is calculated using CIDR notation.

          &lt;div
            class=&quot;gatsby-resp-iframe-wrapper&quot;
            style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden;margin-bottom: 1rem&quot;
          &gt;
            &lt;div class=&quot;embedVideo-container&quot;&gt;
            &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/nFYilGQ-p-8?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0;
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
          &quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
        &lt;/div&gt;
          &lt;/div&gt;
          &lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f8bd199a1b34763862a110bfe93482e2/44d84/allow-own-ip2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35.153583617747444%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA6klEQVQoz51RSW7DMAzUC2ztW+yasmSnQZAC/f/jpqAKpcsxhwGHIw5FUeKyNdzuD9wfn3ijHVJKKKVehjA+IV8WrMsCrVRv+Cp6Q7ccqO3E7f2KdV0RQkCMsceUUkfO6cl/tPwn9yHBWAexEWFvB0o9QFRQSgER9cim38bBOfKlgzNCTNDGQpTaQHuDjxnWOjjnEFOCtbY/Y5omaK0xz3PnDObckPWnNs3dI1KKsMZAyrnvwBiDWiu8993A2rZtOM+zn7HGkafiGmM05KTQPiLalSB4omH+Xzw0ruGdjpwxPuGbK9DikWPAF1j2r+PrA0hSAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Allow list own ip&quot;
        title=&quot;Allow list own ip&quot;
        src=&quot;/static/f8bd199a1b34763862a110bfe93482e2/463a7/allow-own-ip2.png&quot;
        srcset=&quot;/static/f8bd199a1b34763862a110bfe93482e2/8890b/allow-own-ip2.png 293w,
/static/f8bd199a1b34763862a110bfe93482e2/1f316/allow-own-ip2.png 585w,
/static/f8bd199a1b34763862a110bfe93482e2/463a7/allow-own-ip2.png 1170w,
/static/f8bd199a1b34763862a110bfe93482e2/44d84/allow-own-ip2.png 1399w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;
This is how it should look after the rule is added.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Note that you may have to wait few minutes for the rule to propagate. Then trigger the function from the portal:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9b983718bfa200e3ebaeab1418fe48bb/6f9cd/success.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.93174061433447%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABP0lEQVQoz22Sa26DMBCEOQFgSJAo+I3BBgJUaXv/m021m4eUNj8+WVp7Z8ZeZ2G7IsUJ1lr0fY+6rlEUJYQQEFWF6k5Zli8URf6vRmTKOny0LTe1bQutJJTSUNqg63pIqbhO+2TGBnWNppU4nZu7Yf00zr6+fxCnCd57GGsRU8I6JzhnobWGlJKTS0JKdF3Hq/cBTdOgKIrXhNcjQRuL8+mEXkrE9cBxfGJZVwzDAG3MDW3YgMRp3bYLxnGEc+4J1bOqEihLgTzP2X0YJ4wxIU4jpjAgjgFjGBhrDQvSEyilOCmJPKBaRjGFuMVlwSnhsh/Ytg3zPCOlhDklTkNN3V2Qzr4j42kKwYJ9f0u4bxcc+8Zi+7pgSRHBO8Zbw8/z6PvLiyBdgb6PdZ5TEZdlRgwBzmgMtGcMT/vdl6Gh/AKsmtPt9pZRmAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Getting own ip&quot;
        title=&quot;Getting own ip&quot;
        src=&quot;/static/9b983718bfa200e3ebaeab1418fe48bb/463a7/success.png&quot;
        srcset=&quot;/static/9b983718bfa200e3ebaeab1418fe48bb/8890b/success.png 293w,
/static/9b983718bfa200e3ebaeab1418fe48bb/1f316/success.png 585w,
/static/9b983718bfa200e3ebaeab1418fe48bb/463a7/success.png 1170w,
/static/9b983718bfa200e3ebaeab1418fe48bb/6f9cd/success.png 1578w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that’s it, now you should be able to trigger your function.&lt;/p&gt;
&lt;h3&gt;Quick detour to wording conventions&lt;/h3&gt;
&lt;p&gt;As a quick detour, the &lt;a href=&quot;https://www.bbc.co.uk/news/technology-53050955&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;tech industry has recently started using more explicit terminology&lt;/a&gt; and removing old wordings which both do not make complete sense and can be offensive. Hence the term, allow list. It will be a slow change, let’s try.&lt;/p&gt;
&lt;h2&gt;Remove before production&lt;/h2&gt;
&lt;p&gt;Once your development phase is done, definitely remove any unwanted IPs you have added. I normally recommend having a unit test which can use &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/azure/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;REST API&lt;/a&gt; to assert what rules should be present, before the function is deployed to production.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hope this was useful. I am slowly learning new techniques to work with Azure functions. Please do share your learnings. If you have any thoughts or comments please do get in touch with me on twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the Disqus plugin below.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Debugging an Event Grid Triggered Azure Function]]></title><description><![CDATA[Event grid trigger Azure function We can use Azure Event Grid with an Azure function as an endpoint to handle the events. With great…]]></description><link>https://rubberduckdev.com/debugging-eg-function/</link><guid isPermaLink="false">https://rubberduckdev.com/debugging-eg-function/</guid><pubDate>Fri, 26 Jun 2020 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Event grid trigger Azure function&lt;/h1&gt;
&lt;p&gt;We can use &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Event Grid&lt;/a&gt; with an Azure function as an endpoint to handle the events. With great development tools available for Azure functions with &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-develop-vs&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Visual Studio&lt;/a&gt;, we can debug into our function with a real/mock event grid event trigger.&lt;/p&gt;
&lt;p&gt;This post needs an understanding of the basics of Azure functions development and event grid.&lt;/p&gt;
&lt;h1&gt;Debugging with a real event from Azure&lt;/h1&gt;
&lt;p&gt;For this technique, we need to allow Azure to be able to call the function which we have on our local machine. The steps to follow are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start debugging locally in Visual Studio (hit F5).&lt;/li&gt;
&lt;li&gt;Note the port number from the debug console. It normally is &lt;code class=&quot;language-text&quot;&gt;7071&lt;/code&gt;.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f9f4eeb36f7b6952144deba6621f269d/c5236/debugging-vs.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.48805460750854%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABsklEQVQoz2WRXYucMBSGvdyJGicxiTExjjHxe9TxqzPbsqX0otCLQi/6C/r/f0RRtoXuPrwcwuE8HA5x1m+/5s/fH5++LMtjmrbbbZmmZZrWbbtvH5637Xmet9ttXdb7/f5xXe/9dbz+jTP8+N19/dnNaz1Mha2MMTrPhZQRiziL+AElxPchhPAoEMIABoGQiWMvSVU347x1wzgMQ9s2mdZEa5KmoVIBpZ7vg9Ppv4DT6emJEOLkRVl007ws83Qbx7FtW601zfOoKEiaBoQEGEOEIEIBQvB8DvDxQIhQ6piiNEVpy1qbwl67zFqpNbOW7bJCnGOZoJjjOMZCIM73jhBICBrHjikrnZuy603b9X3fNE1mTFRVUVFEZXmm1AXAc903AQCEGDumbnSeG1tUdb3LdX3RGsuEXC6hSs6U+r7vvcN13TAMndxarXW2o699v99sLc8Nyw3ikQfAMev92+l7nu/7r7LWWiml0tRk2cs4vIzjo20fw7B2nVSKck44Z1IypXiaMikJYxBCAADG+FVODy5JkkqZSpkIkUgZJ0kUx/tfC8Gl5FJGQrAoYgdxHP8BniFERrYjghoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Debugging with VS&quot;
        title=&quot;Debugging with VS&quot;
        src=&quot;/static/f9f4eeb36f7b6952144deba6621f269d/463a7/debugging-vs.png&quot;
        srcset=&quot;/static/f9f4eeb36f7b6952144deba6621f269d/8890b/debugging-vs.png 293w,
/static/f9f4eeb36f7b6952144deba6621f269d/1f316/debugging-vs.png 585w,
/static/f9f4eeb36f7b6952144deba6621f269d/463a7/debugging-vs.png 1170w,
/static/f9f4eeb36f7b6952144deba6621f269d/c5236/debugging-vs.png 1255w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/br&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now we need to allow Azure to be able to call our function. For this, we use the &lt;a href=&quot;https://ngrok.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;ngrok&lt;/a&gt; utility. Get the utility then run the command&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ngrok http -host-header=localhost 7071&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;From the ngrok run output, copy the https URL.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1107px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f07523d786f787e6c4f71a40dbe74189/5d769/ngrok-console.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 22.18430034129693%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAIAAAABPYjBAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAnElEQVQI12XPTQsCIRSFYZeNpl6V0u71YxiJiUZw1yrq//+rqKEI5tm/Bw5z8XjIwaHX1kmt4KABwAAo+bbfkFJaa40xAMAuy0yFjAU+cKXUeJ4oxhORUmo3DOKDcy6E+PXii7Wl1VqllFwIB3Bv7dH7s/fbPF9TIsSUUiklhPDfrxhFIqR1z2g9Io5EU0o155ozIcYYvfcAsL3wAoCREpr68F2yAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Ngrok console&quot;
        title=&quot;Ngrok console&quot;
        src=&quot;/static/f07523d786f787e6c4f71a40dbe74189/5d769/ngrok-console.png&quot;
        srcset=&quot;/static/f07523d786f787e6c4f71a40dbe74189/8890b/ngrok-console.png 293w,
/static/f07523d786f787e6c4f71a40dbe74189/1f316/ngrok-console.png 585w,
/static/f07523d786f787e6c4f71a40dbe74189/5d769/ngrok-console.png 1107w&quot;
        sizes=&quot;(max-width: 1107px) 100vw, 1107px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/br&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We then use this on our Azure event grid subscription endpoint in the lines of&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;https://aeccb7806acc.ngrok.io/runtime/webhooks/EventGrid?functionName=Function1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that’s it. When there is a new event, ngrok will redirect the endpoint call to the function on our development machine.&lt;/p&gt;
&lt;h1&gt;Debugging locally with mock events&lt;/h1&gt;
&lt;p&gt;For locally creating mocked up events, we need to understand the schema of the event.&lt;/p&gt;
&lt;h2&gt;Creating the event JSON&lt;/h2&gt;
&lt;p&gt;Check the event schema on &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/event-schema#event-schema&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft documentation&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[
  {
    &quot;topic&quot;: string,
    &quot;subject&quot;: string,
    &quot;id&quot;: string,
    &quot;eventType&quot;: string,
    &quot;eventTime&quot;: string,
    &quot;data&quot;:{
      object-unique-to-each-publisher
    },
    &quot;dataVersion&quot;: string,
    &quot;metadataVersion&quot;: string
  }
]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Event data&lt;/h3&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;data&lt;/code&gt; element varies depending on the different event sources. Say for example we want to mock the events from Azure subscription as an event grid source. An example event can be found in &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions#example-event&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It looks like&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[{
  &quot;subject&quot;: &quot;/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-name}&quot;,
  &quot;eventType&quot;: &quot;Microsoft.Resources.ResourceWriteSuccess&quot;,
  &quot;eventTime&quot;: &quot;2018-07-19T18:38:04.6117357Z&quot;,
  &quot;id&quot;: &quot;4db48cba-50a2-455a-93b4-de41a3b5b7f6&quot;,
  &quot;data&quot;: {
    &quot;authorization&quot;: {
      &quot;scope&quot;: &quot;/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-name}&quot;,
      &quot;action&quot;: &quot;Microsoft.Storage/storageAccounts/write&quot;,
      &quot;evidence&quot;: {
        &quot;role&quot;: &quot;Subscription Admin&quot;
      }
    },
    &quot;claims&quot;: {
      &quot;aud&quot;: &quot;{audience-claim}&quot;,
      &quot;iss&quot;: &quot;{issuer-claim}&quot;,
      &quot;iat&quot;: &quot;{issued-at-claim}&quot;,
      &quot;nbf&quot;: &quot;{not-before-claim}&quot;,
      &quot;exp&quot;: &quot;{expiration-claim}&quot;,
      &quot;_claim_names&quot;: &quot;{\&quot;groups\&quot;:\&quot;src1\&quot;}&quot;,
      &quot;_claim_sources&quot;: &quot;{\&quot;src1\&quot;:{\&quot;endpoint\&quot;:\&quot;{URI}\&quot;}}&quot;,
      &quot;http://schemas.microsoft.com/claims/authnclassreference&quot;: &quot;1&quot;,
      &quot;aio&quot;: &quot;{token}&quot;,
      &quot;http://schemas.microsoft.com/claims/authnmethodsreferences&quot;: &quot;rsa,mfa&quot;,
      &quot;appid&quot;: &quot;{ID}&quot;,
      &quot;appidacr&quot;: &quot;2&quot;,
      &quot;http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier&quot;: &quot;{ID}&quot;,
      &quot;e_exp&quot;: &quot;{expiration}&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname&quot;: &quot;{last-name}&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname&quot;: &quot;{first-name}&quot;,
      &quot;ipaddr&quot;: &quot;{IP-address}&quot;,
      &quot;name&quot;: &quot;{full-name}&quot;,
      &quot;http://schemas.microsoft.com/identity/claims/objectidentifier&quot;: &quot;{ID}&quot;,
      &quot;onprem_sid&quot;: &quot;{ID}&quot;,
      &quot;puid&quot;: &quot;{ID}&quot;,
      &quot;http://schemas.microsoft.com/identity/claims/scope&quot;: &quot;user_impersonation&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier&quot;: &quot;{ID}&quot;,
      &quot;http://schemas.microsoft.com/identity/claims/tenantid&quot;: &quot;{ID}&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name&quot;: &quot;{user-name}&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn&quot;: &quot;{user-name}&quot;,
      &quot;uti&quot;: &quot;{ID}&quot;,
      &quot;ver&quot;: &quot;1.0&quot;
    },
    &quot;correlationId&quot;: &quot;{ID}&quot;,
    &quot;resourceProvider&quot;: &quot;Microsoft.Storage&quot;,
    &quot;resourceUri&quot;: &quot;/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-name}&quot;,
    &quot;operationName&quot;: &quot;Microsoft.Storage/storageAccounts/write&quot;,
    &quot;status&quot;: &quot;Succeeded&quot;,
    &quot;subscriptionId&quot;: &quot;{subscription-id}&quot;,
    &quot;tenantId&quot;: &quot;{tenant-id}&quot;
  },
  &quot;dataVersion&quot;: &quot;2&quot;,
  &quot;metadataVersion&quot;: &quot;1&quot;,
  &quot;topic&quot;: &quot;/subscriptions/{subscription-id}&quot;
}]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Final event JSON&lt;/h3&gt;
&lt;p&gt;We have to merge the above two JSON files to form the final one which should look like&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
    &quot;subject&quot;: &quot;/subscriptions/0not-real-subcription-id1/resourcegroups/resource-group-name/providers/Microsoft.Web/sites/webappname/config/appsettings&quot;,
    &quot;eventType&quot;: &quot;Microsoft.Resources.ResourceWriteSuccess&quot;,
    &quot;eventTime&quot;: &quot;2018-07-19T18:38:04.6117357Z&quot;,
    &quot;id&quot;: &quot;4db48cba-50a2-455a-93w4-de41a3b5b7f6&quot;,
    &quot;data&quot;: {
        &quot;authorization&quot;: {
            &quot;scope&quot;: &quot;/subscriptions/0not-real-subcription-id1/resourceGroups/resource-group-name/providers/Microsoft.Web/sites/webappname/config/appsettings&quot;,
            &quot;action&quot;: &quot;Microsoft.Web/sites/config/write&quot;,
            &quot;evidence&quot;: {
                &quot;role&quot;: &quot;Contributor&quot;,
                &quot;roleAssignmentScope&quot;: &quot;/subscriptions/0not-real-subcription-id1&quot;,
                &quot;roleAssignmentId&quot;: &quot;4885d9f5deb94e8d9008e0489072b146&quot;,
                &quot;roleDefinitionId&quot;: &quot;b24988ac618042a0ab8820f7382dd24c&quot;,
                &quot;principalId&quot;: &quot;8c80ef36f16f4bd4b237799e24c43cb5&quot;,
                &quot;principalType&quot;: &quot;Group&quot;
            }
        },
        &quot;claims&quot;: {
            &quot;aud&quot;: &quot;https://management.core.windows.net/&quot;,
            &quot;iss&quot;: &quot;https://sts.windows.net/49d1-49bc-b63c/&quot;,
            &quot;iat&quot;: &quot;1593077152&quot;,
            &quot;nbf&quot;: &quot;1593077152&quot;,
            &quot;exp&quot;: &quot;1593081052&quot;,
            &quot;http://schemas.microsoft.com/claims/authnclassreference&quot;: &quot;1&quot;,
            &quot;aio&quot;: &quot;AUQAu/sdfdsf+5Bzb5OcRKrcNyHu5w==&quot;,
            &quot;http://schemas.microsoft.com/claims/authnmethodsreferences&quot;: &quot;pwd,rsa,mfa&quot;,
            &quot;appid&quot;: &quot;1950a258-227b-4e31-a9cf-717495945fc2&quot;,
            &quot;appidacr&quot;: &quot;0&quot;,
            &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname&quot;: &quot;Priyadarshee&quot;,
            &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname&quot;: &quot;Dushyant&quot;,
            &quot;groups&quot;: &quot;76e57378-7986-491e-sdf-6220d6a8bb13&quot;,
            &quot;ipaddr&quot;: &quot;193.117.214.98&quot;,
            &quot;name&quot;: &quot;Priyadarshee, Dushyant&quot;,
            &quot;http://schemas.microsoft.com/identity/claims/objectidentifier&quot;: &quot;f0dc630a-b9b9-498a-945d-5c86dd3a9cbc&quot;,
            &quot;onprem_sid&quot;: &quot;S-1-5-21-1102854320-3722712898-1019641694-40955&quot;,
            &quot;puid&quot;: &quot;10032000C1028AF8&quot;,
            &quot;http://schemas.microsoft.com/identity/claims/scope&quot;: &quot;user_impersonation&quot;,
            &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier&quot;: &quot;RctV-GGTWjD_kITSHLSCIJJD4hwLLdO4o7J7Vi5NT5Q&quot;,
            &quot;http://schemas.microsoft.com/identity/claims/tenantid&quot;: &quot;49d1-49bc-b63c&quot;,
            &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name&quot;: &quot;abcdef@abc.def.co.uk&quot;,
            &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn&quot;: &quot;abcdef@abc.def.co.uk&quot;,
            &quot;uti&quot;: &quot;fPTvpJqJ-kib1Ui5IfnqAA&quot;,
            &quot;ver&quot;: &quot;1.0&quot;,
            &quot;wids&quot;: &quot;88d8e3e3-8f55-4a1e-953a-9b9898b8876b&quot;
        },
        &quot;correlationId&quot;: &quot;92db1c00-aa0d-4044-80b0-de64673e1295&quot;,
        &quot;httpRequest&quot;: {
            &quot;clientRequestId&quot;: &quot;3da63058-b6b6-4f1e-a6aa-28420bab4594&quot;,
            &quot;clientIpAddress&quot;: &quot;193.17.210.98&quot;,
            &quot;method&quot;: &quot;PUT&quot;,
            &quot;url&quot;: &quot;https://management.azure.com/subscriptions/0not-real-subcription-id1/resourceGroups/resource-group-name/providers/Microsoft.Web/sites/webappname/config/appsettings?api-version=2018-11-01&quot;
        },
        &quot;resourceProvider&quot;: &quot;Microsoft.Web&quot;,
        &quot;resourceUri&quot;: &quot;/subscriptions/0not-real-subcription-id1/resourceGroups/resource-group-name/providers/Microsoft.Web/sites/webappname/config/appsettings&quot;,
        &quot;operationName&quot;: &quot;Microsoft.Web/sites/config/write&quot;,
        &quot;status&quot;: &quot;Succeeded&quot;,
        &quot;subscriptionId&quot;: &quot;0not-real-subcription-id1&quot;,
        &quot;tenantId&quot;: &quot;49d1-49bc-b63c&quot;
    },
    &quot;dataVersion&quot;: &quot;2&quot;,
    &quot;metadataVersion&quot;: &quot;1&quot;,
    &quot;topic&quot;: &quot;/subscriptions/0not-real-subcription-id1&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We would want to replace the values in the JSON as to what our function is likely to expect.&lt;/p&gt;
&lt;h2&gt;Using postman to create the request&lt;/h2&gt;
&lt;p&gt;Now using &lt;a href=&quot;https://www.postman.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;postman&lt;/a&gt; we create the &lt;code class=&quot;language-text&quot;&gt;POST&lt;/code&gt; request.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We use the URL &lt;code class=&quot;language-text&quot;&gt;http://localhost:7071/runtime/webhooks/EventGrid?functionName=Function1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following headers&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Content-Type = application/json
aeg-event-type = Notification&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These headers are discussed briefly in &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/handler-event-hubs&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Event hub as an event handler for Azure Event Grid events&lt;/a&gt;.
&lt;/br&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 811px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.027303754266214%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA/klEQVQoz4XPC27DIAwAUC6zQj6YQAIsfJKUNAkw9f7XmZas1bpV3ZNlWZZtyajXXEolpdRaM9ZMmm6TW6dhO4/xPNiuoQBVVZXPoHkOl8tlnucYo/eeMSZEy/cQbdfwV9CyLMaYEEL+yFtM6xZjSjnlnHNK6cgppRCCtdY/Qr0zSivvvTH9Mg3L2bteW2d1r51z3vv+xhgjHyHRtqIVWmla1++caQGsLmtKy6oECgBQ3wDA75+VUkVRkN0J47cTxoRggo8Oxt+FlJJSep88oOv1Oo5j13UY47J4jhBirV3XVQhBCLn3kZQSAI6rLzRNwxj7ufm1zDmHHfvP35lPNwNqO/9h7OgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Postman headers&quot;
        title=&quot;Postman headers&quot;
        src=&quot;/static/fad9c757b921114489b932e32b6819f5/63ce0/pm-headers.png&quot;
        srcset=&quot;/static/fad9c757b921114489b932e32b6819f5/8890b/pm-headers.png 293w,
/static/fad9c757b921114489b932e32b6819f5/1f316/pm-headers.png 585w,
/static/fad9c757b921114489b932e32b6819f5/63ce0/pm-headers.png 811w&quot;
        sizes=&quot;(max-width: 811px) 100vw, 811px&quot;
      /&gt;
    &lt;/span&gt;&lt;/br&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;We copy paste the json we created earlier into request body
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6a477e640df023bdf89c07226b758422/5ea5e/pm-body.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 38.907849829351534%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA0ElEQVQY05XNQW7EIAxAUc7STQLYBmIzCRAgysy2y56j1+ixq6StplLVkebpLywvbFVrra3VFPe67m2dhZ3z9FApJa9dlqparbfrtfWt99Za79vWe885p//kksSb1/eXtw8lMTKLCOdZLsITM0+TJzL2EWNRAyn04dJvc5Q0ueiJHRlj9DiMf2h97zQocGGuW5GQJhKHTBDwaKIjvoceLFqD1hDY4/44KorL0vYUiAk82q+r+uz74c88/HZuVFhKjDGgJQS0Vj9DAQAe4Qme8gmdjlkZ38qvJwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Postman body&quot;
        title=&quot;Postman body&quot;
        src=&quot;/static/6a477e640df023bdf89c07226b758422/463a7/pm-body.png&quot;
        srcset=&quot;/static/6a477e640df023bdf89c07226b758422/8890b/pm-body.png 293w,
/static/6a477e640df023bdf89c07226b758422/1f316/pm-body.png 585w,
/static/6a477e640df023bdf89c07226b758422/463a7/pm-body.png 1170w,
/static/6a477e640df023bdf89c07226b758422/5ea5e/pm-body.png 1505w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/br&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that’s it, when we click &lt;code class=&quot;language-text&quot;&gt;Send&lt;/code&gt;, we will hit any breakpoints on our Visual Studio instance.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This has been a learning experience for me and hence considered sharing if it is useful for someone else. I must say the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-debug-event-grid-trigger-local&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;documentaion&lt;/a&gt; provided by Microsoft is very useful in learning these among other tricks. There is also a great blog post by &lt;a href=&quot;https://blog.mcilreavy.com/articles/2018-12/debug-eventgrid-triggered-azure-function&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Paul Mcilreavy&lt;/a&gt; regarding this.&lt;/p&gt;
&lt;p&gt;Overall, these techniques make debugging experience so much easier and we have all Visual Studio tools at our disposal in these scenarios as well. Definite win-win.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Event grid Azure subscription filtering]]></title><description><![CDATA[Event grid Azure Event Grid is designed to build applications with event-based architectures. The events can be published from Azure or…]]></description><link>https://rubberduckdev.com/resource-provider-event-grid/</link><guid isPermaLink="false">https://rubberduckdev.com/resource-provider-event-grid/</guid><pubDate>Tue, 23 Jun 2020 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Event grid&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/overview&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Event Grid&lt;/a&gt; is designed to build applications with event-based architectures. The events can be published from Azure or other sources and can be handled by services on Azure or elsewhere as long as they can subscribe to them. Event Grid has built-in support for events coming from Azure services, like storage blobs and resource groups. We can also design custom topics for our own events to be published onto the event grid.&lt;/p&gt;
&lt;h1&gt;Event grid concepts&lt;/h1&gt;
&lt;p&gt;The key concepts of the event grid are in the diagram below.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4e631ce70b136d48ba0a47eb2bea2338/e8a46/eg-concepts.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.31399317406143%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAACDElEQVQoz32TS2/TQBDHfeTApYJPhBACvgECVRy4ckQcCh8DEOVGKYgLEhLXcgjiFUVwIRF10geNkzix49d637v+o920Fm0lRhqtdj3z3/Hsb4KmaWCbBs2xO6OUIorGqKrK7621SJIEk8nEr9PpFLPZDFEUtTEn+QHOWeMFhFRQ2kDb5pS7M6k0jDFtAf9aQKVBuGAgXHt3wVIpGGvRWHv+OmthjIU2xq8uzhVQCwNlLIIo43jZS1FQiXkpwLgEZcInj1KOmy8GuPa8j6vP+rixOcDvOfPfGBfIiMCsEMipxM8xwbwSCNxBZ1QgqQTigmNRctRe0KAzyrG28d2L3traxaVHXeyEuesqCBVQUoIwiawWOEgolkQgiEuOnd0MccFwlFIcpTUKwn3Shx9jXHz4Ffff7ePe2xEuPPiC972x73NJGNKSYZIxRBnDYEpwmDIEi1Lg26hAnDPvlHEQuhL8tFdgbaOLK09/4frmAJcfd/ExLLxgRTkywv1vUy7R+1PhwAn2pwRPOhOUTGNZK1ChfH+cHWYCd98McWc7xO1XIdZfD7GX8hVaXEBrhYRILGuJz/sVhguGwD1GGBPUXIEw5W/TWuN/5mBxL+yIUFp7lLhUEMo4DlcsOa6ckPUYNC2sZzk8Yc/FOHfCTrAFO89zzOK4nQQ3BWfpP1Xd8b6ua8RxjDRN26lxE/YXXBc57y94HOMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;eventgrid-concepts&quot;
        title=&quot;eventgrid-concepts&quot;
        src=&quot;/static/4e631ce70b136d48ba0a47eb2bea2338/463a7/eg-concepts.png&quot;
        srcset=&quot;/static/4e631ce70b136d48ba0a47eb2bea2338/8890b/eg-concepts.png 293w,
/static/4e631ce70b136d48ba0a47eb2bea2338/1f316/eg-concepts.png 585w,
/static/4e631ce70b136d48ba0a47eb2bea2338/463a7/eg-concepts.png 1170w,
/static/4e631ce70b136d48ba0a47eb2bea2338/e8a46/eg-concepts.png 1282w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Essentially, picked straight from &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/overview#concepts&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;documentation&lt;/a&gt;.
There are five concepts in Azure Event Grid that let you get going:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Events&lt;/strong&gt; - What happened.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event sources&lt;/strong&gt; - Where the event took place.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Topics&lt;/strong&gt; - The endpoint where publishers send events.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event subscriptions&lt;/strong&gt; - The endpoint or built-in mechanism to route events, sometimes to more than one handler. Subscriptions are also used by handlers to intelligently filter incoming events.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event handlers&lt;/strong&gt; - The app or service reacting to the event.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Subscribing to Azure subscription topic&lt;/h1&gt;
&lt;p&gt;So in this post, we will focus on subscribing to events such as changes that happen to resources on Azure. This could be a file uploaded to blob storage, a new azure function created or something else. The main trick to getting the specific event we want is by using &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/event-filtering&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;filters&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Azure portal&lt;/h2&gt;
&lt;p&gt;I would not write on this part, as lots of resources are available online including &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/event-filtering&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;documentation&lt;/a&gt;. Providing it here, for the sake of providing the easiest way to try and test.&lt;/p&gt;
&lt;h2&gt;ARM Template&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://azure.microsoft.com/en-gb/resources/templates/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;ARM templates&lt;/a&gt; provide a great means to implement &lt;a href=&quot;https://en.wikipedia.org/wiki/Infrastructure_as_code&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;IaC&lt;/a&gt; and this is the main bit we will focus on in this post.&lt;/p&gt;
&lt;h3&gt;Use case&lt;/h3&gt;
&lt;p&gt;Let’s try to filter events in event grid to be triggered only when an Azure function changes in our subscription (say configuration change, code updated or new function created/deleted).&lt;/p&gt;
&lt;h3&gt;Steps to put together the template&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Get the quick start template from &lt;a href=&quot;https://github.com/Azure/azure-quickstart-templates/blob/master/101-event-grid-resource-events-to-webhook/azuredeploy.json&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;101-event-grid-resource-events-to-webhook&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update filters in arm template&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;            &quot;filter&quot;: {
                &quot;isSubjectCaseSensitive&quot;: false,
                &quot;subjectBeginsWith&quot;: &quot;&quot;,
                &quot;subjectEndsWith&quot;: &quot;&quot;,
                &quot;includedEventTypes&quot;: [
                    &quot;Microsoft.Resources.ResourceWriteSuccess&quot;,
                    &quot;Microsoft.Resources.ResourceDeleteSuccess&quot;
                ],
                &quot;advancedFilters&quot;: [
                    {
                        &quot;values&quot;: [
                            &quot;Microsoft.Web/sites/functions/write&quot;,
                            &quot;Microsoft.Web/sites/functions/delete&quot;,
                            &quot;Microsoft.Web/sites/hostruntime/vfs/run.csx/write&quot;,
                            &quot;Microsoft.Web/sites/config/write&quot;
                        ],
                        &quot;operatorType&quot;: &quot;StringIn&quot;,
                        &quot;key&quot;: &quot;data.operationName&quot;
                    }
                ]
            }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As far as I understand all values in &lt;code class=&quot;language-text&quot;&gt;filter&lt;/code&gt; section excluding &lt;code class=&quot;language-text&quot;&gt;advancedFilters&lt;/code&gt; are mandatory, at least how VSCode directed me. For the sake of our use case, we only need to look at &lt;code class=&quot;language-text&quot;&gt;Microsoft.Resources.ResourceWriteSuccess&lt;/code&gt; &amp;#x26; &lt;code class=&quot;language-text&quot;&gt;Microsoft.Resources.ResourceDeleteSuccess&lt;/code&gt; events. The full list of event types for Azure subscription topic can be found at &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions#available-event-types&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Event Grid event schema&lt;/a&gt;. Please note that the Azure subscription is our subscription on Azure and it is not implying subscribing the event grid.&lt;/p&gt;
&lt;p&gt;After getting the right type of events, we would want to filter it down to specific changes in Azure functions. This can be done via &lt;code class=&quot;language-text&quot;&gt;advancedFilters&lt;/code&gt;. First of all, we need to look at an &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions#example-event&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;example event from Azure subscription&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[{
  &quot;subject&quot;: &quot;/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-name}&quot;,
  &quot;eventType&quot;: &quot;Microsoft.Resources.ResourceWriteSuccess&quot;,
  &quot;eventTime&quot;: &quot;2018-07-19T18:38:04.6117357Z&quot;,
  &quot;id&quot;: &quot;4db48cba-50a2-455a-93b4-de41a3b5b7f6&quot;,
  &quot;data&quot;: {
    &quot;authorization&quot;: {
      &quot;scope&quot;: &quot;/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-name}&quot;,
      &quot;action&quot;: &quot;Microsoft.Storage/storageAccounts/write&quot;,
      &quot;evidence&quot;: {
        &quot;role&quot;: &quot;Subscription Admin&quot;
      }
    },
    &quot;claims&quot;: {
      &quot;aud&quot;: &quot;{audience-claim}&quot;,
      &quot;iss&quot;: &quot;{issuer-claim}&quot;,
      &quot;iat&quot;: &quot;{issued-at-claim}&quot;,
      &quot;nbf&quot;: &quot;{not-before-claim}&quot;,
      &quot;exp&quot;: &quot;{expiration-claim}&quot;,
      &quot;_claim_names&quot;: &quot;{\&quot;groups\&quot;:\&quot;src1\&quot;}&quot;,
      &quot;_claim_sources&quot;: &quot;{\&quot;src1\&quot;:{\&quot;endpoint\&quot;:\&quot;{URI}\&quot;}}&quot;,
      &quot;http://schemas.microsoft.com/claims/authnclassreference&quot;: &quot;1&quot;,
      &quot;aio&quot;: &quot;{token}&quot;,
      &quot;http://schemas.microsoft.com/claims/authnmethodsreferences&quot;: &quot;rsa,mfa&quot;,
      &quot;appid&quot;: &quot;{ID}&quot;,
      &quot;appidacr&quot;: &quot;2&quot;,
      &quot;http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier&quot;: &quot;{ID}&quot;,
      &quot;e_exp&quot;: &quot;{expiration}&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname&quot;: &quot;{last-name}&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname&quot;: &quot;{first-name}&quot;,
      &quot;ipaddr&quot;: &quot;{IP-address}&quot;,
      &quot;name&quot;: &quot;{full-name}&quot;,
      &quot;http://schemas.microsoft.com/identity/claims/objectidentifier&quot;: &quot;{ID}&quot;,
      &quot;onprem_sid&quot;: &quot;{ID}&quot;,
      &quot;puid&quot;: &quot;{ID}&quot;,
      &quot;http://schemas.microsoft.com/identity/claims/scope&quot;: &quot;user_impersonation&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier&quot;: &quot;{ID}&quot;,
      &quot;http://schemas.microsoft.com/identity/claims/tenantid&quot;: &quot;{ID}&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name&quot;: &quot;{user-name}&quot;,
      &quot;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn&quot;: &quot;{user-name}&quot;,
      &quot;uti&quot;: &quot;{ID}&quot;,
      &quot;ver&quot;: &quot;1.0&quot;
    },
    &quot;correlationId&quot;: &quot;{ID}&quot;,
    &quot;resourceProvider&quot;: &quot;Microsoft.Storage&quot;,
    &quot;resourceUri&quot;: &quot;/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-name}&quot;,
    &quot;operationName&quot;: &quot;Microsoft.Storage/storageAccounts/write&quot;,
    &quot;status&quot;: &quot;Succeeded&quot;,
    &quot;subscriptionId&quot;: &quot;{subscription-id}&quot;,
    &quot;tenantId&quot;: &quot;{tenant-id}&quot;
  },
  &quot;dataVersion&quot;: &quot;2&quot;,
  &quot;metadataVersion&quot;: &quot;1&quot;,
  &quot;topic&quot;: &quot;/subscriptions/{subscription-id}&quot;
}]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the example event JSON, notice &lt;code class=&quot;language-text&quot;&gt;operationName&lt;/code&gt;. The full list of available operations can be found at &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/role-based-access-control/resource-provider-operations&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure resource providers operations&lt;/a&gt;. For our use case, we only need to look at specific operations from that list. And those values can be accessed via the &lt;code class=&quot;language-text&quot;&gt;data&lt;/code&gt; object as in our template.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;                    &quot;advancedFilters&quot;: [
                        {
                            &quot;values&quot;: [
                                &quot;Microsoft.Web/sites/functions/write&quot;,
                                &quot;Microsoft.Web/sites/functions/delete&quot;,
                                &quot;Microsoft.Web/sites/hostruntime/vfs/run.csx/write&quot;,
                                &quot;Microsoft.Web/sites/config/write&quot;
                            ],
                            &quot;operatorType&quot;: &quot;StringIn&quot;,
                            &quot;key&quot;: &quot;data.operationName&quot;
                        }
                    ]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Final ARM template should look like the one below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
&quot;$schema&quot;: &quot;https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#&quot;,
&quot;contentVersion&quot;: &quot;1.0.0.0&quot;,
&quot;parameters&quot;: {
    &quot;eventSubName&quot;: {
        &quot;type&quot;: &quot;string&quot;,
        &quot;defaultValue&quot;: &quot;subToResources&quot;,
        &quot;metadata&quot;: {
            &quot;description&quot;: &quot;The name of the event subscription to create.&quot;
        }
    },
    &quot;endpoint&quot;: {
        &quot;type&quot;: &quot;string&quot;,
        &quot;metadata&quot;: {
            &quot;description&quot;: &quot;The URL for the WebHook to receive events. Create your own endpoint for events.&quot;
        }
    }
},
&quot;resources&quot;: [
    {
        &quot;type&quot;: &quot;Microsoft.EventGrid/eventSubscriptions&quot;,
        &quot;name&quot;: &quot;[parameters(&apos;eventSubName&apos;)]&quot;,
        &quot;apiVersion&quot;: &quot;2018-01-01&quot;,
        &quot;properties&quot;: {
            &quot;destination&quot;: {
                &quot;endpointType&quot;: &quot;WebHook&quot;,
                &quot;properties&quot;: {
                    &quot;endpointUrl&quot;: &quot;[parameters(&apos;endpoint&apos;)]&quot;
                }
            },
            &quot;filter&quot;: {
                &quot;isSubjectCaseSensitive&quot;: false,
                &quot;subjectBeginsWith&quot;: &quot;&quot;,
                &quot;subjectEndsWith&quot;: &quot;&quot;,
                &quot;includedEventTypes&quot;: [
                    &quot;Microsoft.Resources.ResourceWriteSuccess&quot;,
                    &quot;Microsoft.Resources.ResourceDeleteSuccess&quot;
                ],
                &quot;advancedFilters&quot;: [
                    {
                        &quot;values&quot;: [
                            &quot;Microsoft.Web/sites/functions/write&quot;,
                            &quot;Microsoft.Web/sites/functions/delete&quot;,
                            &quot;Microsoft.Web/sites/hostruntime/vfs/run.csx/write&quot;,
                            &quot;Microsoft.Web/sites/config/write&quot;
                        ],
                        &quot;operatorType&quot;: &quot;StringIn&quot;,
                        &quot;key&quot;: &quot;data.operationName&quot;
                    }
                ]
            }
        }
    }
]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Create an Azure function with &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-grid-trigger?tabs=csharp&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;event grid trigger&lt;/a&gt;. This function must exist before we deploy the arm template.&lt;/li&gt;
&lt;li&gt;Deploy arm template with &lt;code class=&quot;language-text&quot;&gt;endpoint&lt;/code&gt; parameter pointing to the function URL we created. Ensure they have the right keys or the handshaking will fail.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Credits&lt;/h3&gt;
&lt;p&gt;I must mention these two gentlemen who helped me while I was trying to figure out how best to create the arm template. &lt;a href=&quot;https://twitter.com/dracan&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Dan Clarke&lt;/a&gt; for the initial tip to start investigating towards event grid for this use case and &lt;a href=&quot;https://stackoverflow.com/users/8084828/roman-kiss&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Roman Kiss&lt;/a&gt; for answering my question on &lt;a href=&quot;https://stackoverflow.com/questions/62389029/event-grid-filter-events-to-all-azure-functions-in-subscription&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;stackoverflow&lt;/a&gt; and helping me out with the filters.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;It has been a satisfying learning experience figuring out the best means to trigger endpoints with events coming from resources. Event grid makes this very useful and easy. The possibilities are endless, we can have functions and services which act as per behaviour from resources. It makes automation of Azure resources easier too. Hope this post helps you in your efforts with event grid and arm templates. Do share thoughts and opinions on twitter or here.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Obfuscation using nuget]]></title><description><![CDATA[Obfuscation We are in the golden age of open source software and yet at least I (surely many others too) work at places where safeguarding…]]></description><link>https://rubberduckdev.com/obfuscation-using-nuget/</link><guid isPermaLink="false">https://rubberduckdev.com/obfuscation-using-nuget/</guid><pubDate>Thu, 04 Jun 2020 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Obfuscation&lt;/h1&gt;
&lt;p&gt;We are in the golden age of open source software and yet at least I (surely many others too) work at places where safeguarding intellectual property (IP) in software is of paramount importance. One of the main techniques used to safeguard IP is by &lt;a href=&quot;https://en.wikipedia.org/wiki/Obfuscation_(software)&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;obfuscating the code&lt;/a&gt; we ship. We can achieve this by a simple nuget as well. Let’s dig into the details in this post.&lt;/p&gt;
&lt;h1&gt;Nuget&lt;/h1&gt;
&lt;p&gt;This post mainly aims at .net framework project, msbuild and nuget.&lt;/p&gt;
&lt;h2&gt;.Net framework&lt;/h2&gt;
&lt;p&gt;You may think why are we still talking about .net framework when &lt;a href=&quot;https://devblogs.microsoft.com/dotnet/net-core-is-the-future-of-net/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;.NET Core is the Future of .NET&lt;/a&gt; &amp;#x26; &lt;a href=&quot;https://devblogs.microsoft.com/dotnet/introducing-net-5/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;.NET 5 is round the corner&lt;/a&gt;. We are getting cool features with the latest developments in the dotnet world, but again from experience, we know that migration and adoption of the newer technologies will take some time, I mean years. Until then we will have a codebase of only .net framework or a mix of .net framework and dotnet core/standard etc.
&lt;br/&gt;
&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=rubberduckdev.SDKStyleTemplatesNet&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 429px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.6382252559727%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAA6cAAAOnAEHlFPdAAABiElEQVQoz42QTUsbURSGZ5XYtUbJIpAfkpRslAqVMI3+Ct1bxF8h7kRcd6X4AwRRWxjzYTVmoNVkPvLtfGdS7eqRuZOoBRddPJxzz7335X2PlM1mWZifJ5VKMTs3SyaTIZ1O82FmhmQyKUgkEv+NtLi0hCx/YfnTMsWiLOrnlRVWV9colUrIskyhUCCXz5PPfxREfS73PpJudmhqBvctDc1oC5otnbumNpnrtHSTlmaIeXSO3kT/XmmjGzHSaPyI4wcMLIvwzxOeP0JRypyeXaCUK1RqV3z/oaCUq9SurrksV7FcH380xvECvCAU/RTJdn1x4XojUS3b5bqhUldVbtQGP+u31BsqutnF7PaEu3a3j9np8ev3/cRtm05/SG/wgOT6sZAQnfTeQ4gzCAjsR/o9i6amx5F1Q6whWkG8GkMIv9UQgi9CQUjfstg62mbz8Cvr3zY4rBwTBk8MbUdEtRyPKJX9po81YkP/CvohQ9tm72yf/fMDdk52OVXPGYd/XxK8x9Rd5PAZvgPxSJcMQEsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;sdk-style-templates&quot;
        title=&quot;sdk-style-templates&quot;
        src=&quot;/static/e492ef12812e361d01ee3b4a2af8d9b3/7cfe8/sdk-style-templates.png&quot;
        srcset=&quot;/static/e492ef12812e361d01ee3b4a2af8d9b3/8890b/sdk-style-templates.png 293w,
/static/e492ef12812e361d01ee3b4a2af8d9b3/7cfe8/sdk-style-templates.png 429w&quot;
        sizes=&quot;(max-width: 429px) 100vw, 429px&quot;
      /&gt;
    &lt;/span&gt;&lt;/a&gt;
&lt;br/&gt;&lt;br/&gt;
Having said so, the sdk style csproj is very useful, feel free to check &lt;a href=&quot;https://www.rubberduckdev.com/sdkstyle-framework-csproj/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;my earlier blog post&lt;/a&gt; regarding that. But as of now, we do not have an sdk style csproj template shipped in Visual Studio for .net framework projects. I use the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=rubberduckdev.SDKStyleTemplatesNet&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;simple visual studio extension&lt;/a&gt; above, which is open source and any comments/suggestions are welcome on that.&lt;/p&gt;
&lt;h2&gt;NuGet techniques&lt;/h2&gt;
&lt;p&gt;Before we jump into the obfuscation nuget development, let me show a few useful tips with nuget, which we will use.&lt;/p&gt;
&lt;h2&gt;GeneratePackageOnBuild&lt;/h2&gt;
&lt;p&gt;In sdk style csproj, we can add &lt;code class=&quot;language-text&quot;&gt;&amp;lt;GeneratePackageOnBuild&gt;true&amp;lt;/GeneratePackageOnBuild&gt;&lt;/code&gt; which can automatically generate the nupkg file for us upon build. This makes it much easier, especially if you come from the times when we had to use nuspec files. Now all references, nuget properties etc can all stay in the csproj. Refer &lt;a href=&quot;https://docs.microsoft.com/en-us/nuget/quickstart/create-and-publish-a-package-using-the-dotnet-cli#automatically-generate-package-on-build&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Automatically generate package on build&lt;/a&gt; for documentation.&lt;/p&gt;
&lt;h2&gt;NuGet package folder structure&lt;/h2&gt;
&lt;p&gt;Microsoft has a &lt;a href=&quot;https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package#from-a-convention-based-working-directory&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;convention-based directory structure&lt;/a&gt; for the nupkg file. A nupkg file is essentially a zip file and the different folders mean something specific. For example, the one we are going to use the &lt;code class=&quot;language-text&quot;&gt;build&lt;/code&gt; folder that can have targets and props file which will be triggered during the build of the project that references our nupkg. &lt;em&gt;Note&lt;/em&gt; that the props or targets file name in the build folder has to be the same as the package id.&lt;/p&gt;
&lt;h2&gt;MSBuild props and targets in nupkg&lt;/h2&gt;
&lt;p&gt;This is what we discussed a bit in the previous point. Any props files in the build folder are imported at the top of the consuming project and any targets are imported at the end of the csproj. The convention is that they have to be named after the package id, else they will be ignored.&lt;/p&gt;
&lt;h2&gt;IncludeBuildOutput&lt;/h2&gt;
&lt;p&gt;Another important property which we can include in sdk style csproj is &lt;code class=&quot;language-text&quot;&gt;&amp;lt;IncludeBuildOutput&gt;false&amp;lt;/IncludeBuildOutput&gt;&lt;/code&gt;. This is so that if say the objective of the nupkg is just to ship a targets file (which is what we will do in a bit), it doesn’t need to include any build output. &lt;em&gt;Note&lt;/em&gt; that without this property, by default class library projects will include an empty dll in the nupkg.&lt;/p&gt;
&lt;h2&gt;Demo plan&lt;/h2&gt;
&lt;p&gt;As presented, in the architecture diagram below, we will have a .net framework project that generates a nupkg. This package is referenced by a second .net framework project. When the second project builds, it automatically triggers the obfuscation of the generated dll.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 885px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b84c1ded361274098ab3e628cef125b5/25849/architecture.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.73378839590443%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAA6cAAAOnAEHlFPdAAAB9ElEQVQoz22Sy2sTURTGb1JxoQsRXAgSi10pFgQrUsGFhu7UFgQ3IujSjbhRiigYNdNMrYnJZCaTxDQ175o2MY+hseKjPkLxUUUosRtFFPTP+MnchESri8N3L+ec757vO1eI8C2EdgOJEV87TB9OU+Wiv8CAFmRHNsopq85orcJYvcrW6ZCsc5qTOMxJhKl2+lTEUDHD0XKRA8UMwk5GVJlwhBVGAjE2B30cr1dYav2i/ukbjdZPBrJxROg6QvcidHuQiR5p6OU7ys8+4H+9wngtxWg+gtC8OO1kTEEYXrYndQ6VZjlYyrO/mMWVMhkszMjYW5hhU9zfIz08X+BE9SHD8wWOZcMMJf0IfQKHqeKMqBKFriBCnvY0gWuMLVhYK1+493iZ+uoPBmdT0jJZK/27e7UtIWxP1CbretJB29MNth0hD/3ZKOcWGpytVDizuMiWpCbly75dSYPT5Qa70wkpr0/6+DdZ11dDwZ1JM5ybYySnYVj35SAuc4r+eKBd704nWH3zmfNPH8nX+6J/bK279c49qnLbatB8v8a+fIyd8SlE0MOlaokLjQpCu4nYlgjgfjCNK63/K/c/5z0pnSNzSTbG70iZJ60anudNrjx5weWlJkJuJ+xtG7+eYD2pjYbS+7OGwvirZaofv5J7u0ax9Z3f6nengWh+3DIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;architecture&quot;
        title=&quot;architecture&quot;
        src=&quot;/static/b84c1ded361274098ab3e628cef125b5/25849/architecture.png&quot;
        srcset=&quot;/static/b84c1ded361274098ab3e628cef125b5/8890b/architecture.png 293w,
/static/b84c1ded361274098ab3e628cef125b5/1f316/architecture.png 585w,
/static/b84c1ded361274098ab3e628cef125b5/25849/architecture.png 885w&quot;
        sizes=&quot;(max-width: 885px) 100vw, 885px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;The full code is on &lt;a href=&quot;https://github.com/realrubberduckdev/NuGetAndMSBuild&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;github&lt;/a&gt; and I will run through the csproj file here which generates the nuget package to help obfuscate the output dll of the referencing project.&lt;/p&gt;
&lt;h3&gt;RDD.Obfuscation.AfterBuild.csproj&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt;
  &amp;lt;PropertyGroup&gt;
    &amp;lt;TargetFramework&gt;net48&amp;lt;/TargetFramework&gt;
    &amp;lt;GeneratePackageOnBuild&gt;true&amp;lt;/GeneratePackageOnBuild&gt;
    &amp;lt;PackageId&gt;RDD.Obfuscation.AfterBuild&amp;lt;/PackageId&gt;
    &amp;lt;IncludeBuildOutput&gt;false&amp;lt;/IncludeBuildOutput&gt;
  &amp;lt;/PropertyGroup&gt;
  &amp;lt;ItemGroup&gt;
    &amp;lt;PackageReference Include=&quot;ConfuserEx.Final&quot; Version=&quot;1.0.0&quot; /&gt;
  &amp;lt;/ItemGroup&gt;
  &amp;lt;ItemGroup&gt;
    &amp;lt;None Include=&quot;RDD.Obfuscation.AfterBuild.targets&quot; Pack=&quot;True&quot; PackagePath=&quot;build&quot; /&gt;
  &amp;lt;/ItemGroup&gt;
&amp;lt;/Project&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The RDD.Obfuscation.AfterBuild.csproj produces the obfuscation helper nuget package. The nupkg is a simple nuget package with a targets file shown below.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&amp;lt;Project ToolsVersion=&quot;4.0&quot; DefaultTargets=&quot;Build&quot; xmlns=&quot;http://schemas.microsoft.com/developer/msbuild/2003&quot;&gt;
  &amp;lt;PropertyGroup&gt;

    &amp;lt;!-- ConfuserToolsPath --&gt;
    &amp;lt;ConfuserToolsPath&gt;$(MSBuildThisFileDirectory)..\..\..\ConfuserEx.Final\1.0.0\tools&amp;lt;/ConfuserToolsPath&gt;
    &amp;lt;ConfuserExePath&gt;$(ConfuserToolsPath)\Confuser.CLI.exe&amp;lt;/ConfuserExePath&gt;

    &amp;lt;!-- Confuser command --&gt;
    &amp;lt;ConfuserCommand Condition=&quot; &apos;$(OS)&apos; == &apos;Windows_NT&apos;&quot;&gt;&quot;$(ConfuserExePath)&quot;&amp;lt;/ConfuserCommand&gt;

    &amp;lt;!-- ProjectFileName --&gt;
    &amp;lt;ProjectFileName&gt;$(ProjectDir)Confuser.crproj&amp;lt;/ProjectFileName&gt;

    &amp;lt;!-- Commands --&gt;
    &amp;lt;ConfuseCommand&gt;$(ConfuserCommand) &quot;$(ProjectFileName)&quot;&amp;lt;/ConfuseCommand&gt;
  &amp;lt;/PropertyGroup&gt;

  &amp;lt;Target Name=&quot;Confuser&quot; AfterTargets=&quot;AfterBuild&quot;&gt;
    &amp;lt;Exec WorkingDirectory=&quot;$(ProjectDir)&quot; Command=&quot;$(ConfuseCommand)&quot; /&gt;
  &amp;lt;/Target&gt;
&amp;lt;/Project&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The targets file locates &lt;code class=&quot;language-text&quot;&gt;Confuser.CLI.exe&lt;/code&gt; at nuget packages location and the needed &lt;code class=&quot;language-text&quot;&gt;Confuser.crproj&lt;/code&gt; at the consuming project’s root. Then it triggers obfuscation after target &lt;code class=&quot;language-text&quot;&gt;AfterBuild&lt;/code&gt; has finished. Thus ensuring the generated dll gets obfuscated.&lt;/p&gt;
&lt;h1&gt;MSBuild &amp;#x26; dotnet build&lt;/h1&gt;
&lt;p&gt;To generate the nuget, we can simply run &lt;code class=&quot;language-text&quot;&gt;msbuild RDD.Obfuscation.AfterBuild.csproj&lt;/code&gt; and it will give us &lt;code class=&quot;language-text&quot;&gt;RDD.Obfuscation.AfterBuild.1.0.0.nupkg&lt;/code&gt;. We could also pass in parameters such as verion &lt;code class=&quot;language-text&quot;&gt;msbuild RDD.Obfuscation.AfterBuild.csproj /P:Version=2.0.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Can we do these using dotnet? Yes, we can. Let me demonstrate the relation between msbuild and dotnet by showcasing how nuget restore is implemented.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 463px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.218430034129696%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAA6cAAAOnAEHlFPdAAABeUlEQVQoz22Sy24TQRBFZ0MihLLD7kf1u8dOPDFELIgBA5INBPz/H3TQjGOPHWdxVNXdo1s9fW+jraDF4UKg75WxA33fLZek3JHiglIXlFJx4o7n2toLmlEwXgga50jiMNYy1eb5TNBG9tWe0+81h8UoeMCirFBT2QucDNtjBvpB4zBL42MipIx1iU8fbvixesP68xXrxyu+P17zbXXNdvWOf6sbdlvN7o/l19qy+Wp42hj+bhVPG8Xut/Dzy1saIw7rPOIjzht8UCdo5tVTk6XNgVlbmLWVWgolF9p+PetrIhdPCPr8l5VxTLUcmWjhNlaqS4TYEcM9JT2QnvsYOrzrSHmOEc9EyaUpp44ZEYz3uJjIteBjxrmM85mYMqlkSlsR749GHgV9HF0eXbMobcm15fa+o5S7IUI5dczmC+4WHcuPD8SczwXNi9icR8GSxDMXj4SElv4pFBOled8zVYPLh29fzeFp7fetdQTnsS4Mb6VfzeD+hv8BmdYM4QTvayAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;using-nuget&quot;
        title=&quot;using-nuget&quot;
        src=&quot;/static/b8851e43f45db64480f68bb1a0c6ecaf/51bed/using-nuget.png&quot;
        srcset=&quot;/static/b8851e43f45db64480f68bb1a0c6ecaf/8890b/using-nuget.png 293w,
/static/b8851e43f45db64480f68bb1a0c6ecaf/51bed/using-nuget.png 463w&quot;
        sizes=&quot;(max-width: 463px) 100vw, 463px&quot;
      /&gt;
    &lt;/span&gt;&lt;br/&gt;
In 2016, &lt;a href=&quot;https://github.com/dotnet/cli/blob/6cde21225e18fc48eeab3f4345ece3e6bb122e53/src/dotnet/commands/dotnet-restore/Program.cs#L36&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;dotnet restore used nuget restore&lt;/a&gt; under the hood.
&lt;br/&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 539px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.051194539249146%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAA6cAAAOnAEHlFPdAAABaklEQVQoz3WSWW/TQBRG/dgWIfGCl9lsz+qxaVACrRpKxf//VQfNxCEShYfjT7pzffemF5JBKoTSdIOoCKFwzhFzRkhF2w/VftXyz8VXvqMpnxJMarM7C3opmaxl3TbytuFsxodE3lbGab75/YOmPF4qNHzuJW1fMml6oenEroO+2YZS8f9pSnV6tEyz5HT4wLevDzyfPvHr1fD2Q3H+Ljk/tbydBT9fBK8vA0/HjxwPd5wO97veVT0e7mnKPEpQpTXadGjTMltBzrYSgyM4T87ujy34Ce8s3s2Eq3pLCHpveZ9hu7c8KIMLC9ZHxjFhTGa2EedTtWnjMSahdawoFdAmosf5/VIKyhiWJROXpS5knr4QUyblTEwLIUW2x43ZWdq+px12+oGmq2ejb2cjJEoqvDY4bZjHxDQt2Bp0ZVlXQko4H8jrRlxyXWoprIyvtlxnaEa6/rJxqTRDSVRRF/akf1P8r3dZAv4G0y30NMFGeG0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;using-msbuild&quot;
        title=&quot;using-msbuild&quot;
        src=&quot;/static/76f918e0c35bea81cd86acc55d348ab8/4330c/using-msbuild.png&quot;
        srcset=&quot;/static/76f918e0c35bea81cd86acc55d348ab8/8890b/using-msbuild.png 293w,
/static/76f918e0c35bea81cd86acc55d348ab8/4330c/using-msbuild.png 539w&quot;
        sizes=&quot;(max-width: 539px) 100vw, 539px&quot;
      /&gt;
    &lt;/span&gt;&lt;br/&gt;
In 2019, this has &lt;a href=&quot;https://github.com/dotnet/sdk/blob/master/src/Cli/dotnet/commands/dotnet-restore/Program.cs#L41&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;changed to using msbuild restore&lt;/a&gt;.
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;So, in essence, we are using msbuild under dotnet. But to do msbuild bits, as far as I know, we need to use &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-msbuild&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;dotnet msbuild command&lt;/a&gt;. Which can be an inconvenience if you have mixed (framework and dotnet) codebase.&lt;/p&gt;
&lt;h1&gt;Recommendations&lt;/h1&gt;
&lt;p&gt;Here are a few recommendations if you are using dotnet, msbuild, nuget etc in your development environment.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Migrate to sdk style csproj&lt;/li&gt;
&lt;li&gt;Use MSBuild if the codebase is .Net Framework or mix of core/standard and framework, else use dotnet.&lt;/li&gt;
&lt;li&gt;Migrate to core/standard where possible&lt;/li&gt;
&lt;li&gt;Avoid using multiple tools, msbuild, dotnet and nuget. Very likely you can use just one of those.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Talk video&lt;/h1&gt;
&lt;p&gt;I presented a talk on this topic at &lt;a href=&quot;https://www.dotnetoxford.com/posts/2020-05-lightning-talks&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;.Net Oxford&lt;/a&gt;. Thanks to &lt;a href=&quot;https://twitter.com/dracan&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Dan Clarke&lt;/a&gt;, the video of the talk is now published on youtube.&lt;/p&gt;
&lt;p&gt;
          &lt;div
            class=&quot;gatsby-resp-iframe-wrapper&quot;
            style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden;margin-bottom: 1rem&quot;
          &gt;
            &lt;div class=&quot;embedVideo-container&quot;&gt;
            &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/Bo2kuGnOfKs?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0;
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
          &quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
        &lt;/div&gt;
          &lt;/div&gt;
          &lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;In conclusion, nuget, msbuild, dotnet together give us a powerful combination of tools to make obfuscation or any other similar operations faster, easier &amp;#x26; reusable. Any common shared tool, utility, libraries etc can be easily shared using nuget and I would highly recommend we did so instead of direct assembly/project references. And if you have something amazing to share, definitely publish it to nuget.org and share with all.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[GitHub Actions - Publishing to nuget.org]]></title><description><![CDATA[GitHub Actions GitHub provides GitHub Actions to help with automating workflows. We can design CI-CD pipelines and also apply policies on…]]></description><link>https://rubberduckdev.com/github-multijob-action/</link><guid isPermaLink="false">https://rubberduckdev.com/github-multijob-action/</guid><pubDate>Tue, 02 Jun 2020 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;GitHub Actions&lt;/h1&gt;
&lt;p&gt;GitHub provides &lt;a href=&quot;https://github.com/features/actions&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GitHub Actions&lt;/a&gt; to help with automating workflows. We can design CI-CD pipelines and also apply policies on branches using them. The action workflows can be saved to a git repo as a yml file. Along with build, test &amp;#x26; deploy, GitHub Actions can help with code reviews, branch management and issue triaging as well.&lt;/p&gt;
&lt;h1&gt;Basics&lt;/h1&gt;
&lt;p&gt;The basics of GitHub Actions are covered in &lt;a href=&quot;https://help.github.com/en/actions/getting-started-with-github-actions/core-concepts-for-github-actions&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Core concepts for GitHub Actions&lt;/a&gt;. The overall summary is as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step&lt;/strong&gt;: A step is an individual task that can run commands or actions. Actions are the smallest portable building block of a workflow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job&lt;/strong&gt;: A set of steps that execute on the same runner constitute a job.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflow&lt;/strong&gt;: A configurable automated process, consisting of one or more jobs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflow File&lt;/strong&gt;: The YAML file that defines your workflow configuration with at least one job. This file lives in the root of your GitHub repository in the .github/workflows directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Simple action&lt;/h1&gt;
&lt;p&gt;Let’s have a look at a simple workflow file.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;name: ADOSCloneAllRepos CI-CD

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: windows-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.101
    - name: Install dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore
    - name: Test
      run: dotnet test --no-restore --verbosity normal
    - name: Upload math result for job 2
      uses: actions/upload-artifact@v1
      with:
        name: ADOSCloneAllRepos
        path: ADOSCloneAllRepos\bin\Release\netcoreapp3.1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This workflow, named as &lt;code class=&quot;language-text&quot;&gt;ADOSCloneAllRepos CI-CD&lt;/code&gt;, gets triggered on when we push changes to master or create a pull request on the master. This is controlled by the &lt;code class=&quot;language-text&quot;&gt;on&lt;/code&gt; block,&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After the &lt;code class=&quot;language-text&quot;&gt;on&lt;/code&gt;, we need to start the &lt;code class=&quot;language-text&quot;&gt;jobs&lt;/code&gt; jobs and it needs to specify which kind of agent it needs to run on. Here we specify &lt;code class=&quot;language-text&quot;&gt;runs-on: windows-latest&lt;/code&gt;.
The &lt;code class=&quot;language-text&quot;&gt;steps&lt;/code&gt; now have a set of &lt;code class=&quot;language-text&quot;&gt;uses&lt;/code&gt; which are actions from &lt;a href=&quot;https://github.com/marketplace?type=actions&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GitHub Marketplace&lt;/a&gt;. Some are predefined, such as &lt;code class=&quot;language-text&quot;&gt;run&lt;/code&gt; which executes a command in a prompt.&lt;/p&gt;
&lt;p&gt;The key thing to understand here is, unlike &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/publish-build-artifacts?view=azure-devops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;publish build artefacts task in Azure pipelines&lt;/a&gt;, GitHub Actions uses &lt;a href=&quot;https://github.com/marketplace/actions/upload-a-build-artifact&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;actions/upload-artifact@v1&lt;/a&gt;. Because theoretically, the workflow is uploading the artifact to GitHub.&lt;/p&gt;
&lt;p&gt;So this workflow, builds, tests and publishes a dotnet core artifact.&lt;/p&gt;
&lt;h1&gt;Automated versioning using GitVersion&lt;/h1&gt;
&lt;p&gt;A major challenge in CI-CD and package management is versioning. In simple cases we can use &lt;a href=&quot;https://gitversion.net/docs/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GitVersion&lt;/a&gt; and we can also use it in GitHub Actions workflow. The steps have to change a bit for this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;    steps:
    - uses: actions/checkout@v2
    - name: Fetch all history for all tags and branches
      run: git fetch --prune --unshallow
    - name: Install GitVersion
      uses: gittools/actions/gitversion/setup@v0.9.2
      with:
          versionSpec: &apos;5.2.x&apos;
    - name: Use GitVersion
      id: gitversion # step id used as reference for output values
      uses: gittools/actions/gitversion/execute@v0.9.2
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.101
    - name: Install dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore -p:Version=${{ steps.gitversion.outputs.semVer }}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After the checkout, we need to fetch all history, hence the &lt;code class=&quot;language-text&quot;&gt;run: git fetch --prune --unshallow&lt;/code&gt;. Then we install &lt;code class=&quot;language-text&quot;&gt;GitVersion 5.2.x&lt;/code&gt;, more on this is available at &lt;a href=&quot;https://github.com/marketplace/actions/use-actions&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;use-actions on the marketplace&lt;/a&gt;.
Then we run GitVersion,&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;- name: Use GitVersion
      id: gitversion # step id used as reference for output values
      uses: gittools/actions/gitversion/execute@v0.9.2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice that we provide an id for the step so that we can access the output of the step in the &lt;code class=&quot;language-text&quot;&gt;build&lt;/code&gt; step as &lt;code class=&quot;language-text&quot;&gt;${{ steps.gitversion.outputs.semVer }}&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;GitHub action secret management&lt;/h1&gt;
&lt;p&gt;Secret management is an essential part of any CI-CD pipeline. Especially when it is open source on GitHub. Fortunately, it is very easy to manage our secrets on GitHub Actions. The steps are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add secret on repository’s Secrets settings:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1040px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/df125be036ee76541360868a053770d0/638e9/go-to-settings.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 24.91467576791809%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAxElEQVQY022IR47DQAwE9f9nKjhqEptDUhMNe69baBQatSCrI3NcA5cTv9H1okJaW+ulttq6lep8QlZwpgQiREKpbREKiCFlQ7bI5lOGXAEKsSwqdokai8aEr28Pt25h3QWofSyqxjkzMwAiAoOZCcSM9C0otbbW+o/CrDFdMQqjtrbINaCdbbINtkE6sw29xpxzjjH6/JfeR+9jSZq2c7uHuxd3+OPwt7+zn3sQ96Ln9l69nM/02M/tRQ8v5+F2l9+t1w8Xzh6KtNSQHwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;go-to-settings&quot;
        title=&quot;go-to-settings&quot;
        src=&quot;/static/df125be036ee76541360868a053770d0/638e9/go-to-settings.png&quot;
        srcset=&quot;/static/df125be036ee76541360868a053770d0/8890b/go-to-settings.png 293w,
/static/df125be036ee76541360868a053770d0/1f316/go-to-settings.png 585w,
/static/df125be036ee76541360868a053770d0/638e9/go-to-settings.png 1040w&quot;
        sizes=&quot;(max-width: 1040px) 100vw, 1040px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1057px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/37b1536c04552bddbe5bb04612698e08/cf4b1/secrets.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.02047781569966%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABMElEQVQoz5WR4W7kIAyE8/7veddsswQINh5sTE5hd6v+2Eq9kYWwzGeNzXI/sBULxbajrUnX1Nast6zN3Lqr9e4jF9pCrII9JmYOMRFXtb6wNKooLFSRqT7TCmLhKlUgaAcxcaUqOYb8+Qe1NusXTOUwVbPu7iKSc1ZVdzez3vs8/FG1mVn38dISDhH1cwpACAHA+Tstsbr12ec8BQh7jCkfhdBM0PZ0+FX5AQ7FtPs5cTPb7uH2uaWUgSYiRCxoQEO7JK1rH99gdvRxjuFjuHvTa8HjaeXS4+5T3Yd/87Fsa5Rc5qNTREK4MxOmRARotQqA8c78Ej+C7vsjqdA1lFsohYiYiZ7BzO9nTrkIFO0K6y7qUPeX7a9feQ//DSXSxTW18fNi38NjTvu1m/+C/wENe7/eT/pYmQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;secrets&quot;
        title=&quot;secrets&quot;
        src=&quot;/static/37b1536c04552bddbe5bb04612698e08/cf4b1/secrets.png&quot;
        srcset=&quot;/static/37b1536c04552bddbe5bb04612698e08/8890b/secrets.png 293w,
/static/37b1536c04552bddbe5bb04612698e08/1f316/secrets.png 585w,
/static/37b1536c04552bddbe5bb04612698e08/cf4b1/secrets.png 1057w&quot;
        sizes=&quot;(max-width: 1057px) 100vw, 1057px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1117px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5838d0ff731b6bf0c8d4491675babff3/14e0d/add-new-secret.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.249146757679185%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAyUlEQVQY05WQS3LEIAwFff+bZmOD/sATTOFxKsku84qV6JYEh7WMnvPOGGCWzFx/M4F0T7MZIebE8q4f5CDPnHOt2Xs7z4uYWYSZmbhWUrNwNxYXCVPiXdz4WkdRaMu19uTeOxETEVUqpZZSr+tiZjOzCAk0bCxz/kxmx+1OJLQ7N9XuQGZuDoCam0frg9RNtLk/cjUUe+SNJu7z3XyvtO5G+18AYOz7RxbmPvINzbk+ykHqHm0AfewHfSafVb9OtRjRxq9l/yW/AMB51tikvha5AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;add-new-secret&quot;
        title=&quot;add-new-secret&quot;
        src=&quot;/static/5838d0ff731b6bf0c8d4491675babff3/14e0d/add-new-secret.png&quot;
        srcset=&quot;/static/5838d0ff731b6bf0c8d4491675babff3/8890b/add-new-secret.png 293w,
/static/5838d0ff731b6bf0c8d4491675babff3/1f316/add-new-secret.png 585w,
/static/5838d0ff731b6bf0c8d4491675babff3/14e0d/add-new-secret.png 1117w&quot;
        sizes=&quot;(max-width: 1117px) 100vw, 1117px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;Then access secret in workflow file as &lt;code class=&quot;language-text&quot;&gt;${{ secrets.NUGET_ORG_API_KEY }}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that &lt;a href=&quot;https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#about-the-github_token-secret&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GITHUB_TOKEN&lt;/a&gt; is a special predefined secret.&lt;/p&gt;
&lt;h1&gt;Multijob action&lt;/h1&gt;
&lt;p&gt;A single workflow can have one or more jobs. For the case we have been working on, let’s have two jobs. One to &lt;code class=&quot;language-text&quot;&gt;Build and test&lt;/code&gt; and second one to &lt;code class=&quot;language-text&quot;&gt;Publish to nuget.org&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;jobs:
  build:
    name: Build and test
    runs-on: windows-latest

    steps:
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.101
    - name: Install dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore -p:Version=1.0.0
    - name: Test
      run: dotnet test --no-restore --verbosity normal
    - name: Upload ADOSCloneAllRepos
      uses: actions/upload-artifact@v2
      with:
        name: ADOSCloneAllRepos
        path: ADOSCloneAllRepos\bin\Release\ADOSCloneAllRepos.1.0.0.nupkg

  publish_package:
    name: Publish to nuget.org
    needs: build
    runs-on: windows-latest

    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v1
        with:
          name: ADOSCloneAllRepos
      - name: Setup .NET Core
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 3.1.101
      - name: Push package to nuget.org
        run: dotnet nuget push ./ADOSCloneAllRepos/ADOSCloneAllRepos.1.0.0.nupkg -k ${{ secrets.NUGET_ORG_API_KEY }} -s https://api.nuget.org/v3/index.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first job, builds, tests and uploads artifact to GitHub. The second &lt;code class=&quot;language-text&quot;&gt;publish_package&lt;/code&gt; job gets the artifact from the previous job and publishes to nuget.org.
Notice the &lt;code class=&quot;language-text&quot;&gt;needs: build&lt;/code&gt;. That tells the workflow that &lt;code class=&quot;language-text&quot;&gt;publish_package&lt;/code&gt; has a dependency on &lt;code class=&quot;language-text&quot;&gt;build&lt;/code&gt; job and hence has to run sequentially. The jobs will run in parallel by default.&lt;/p&gt;
&lt;h2&gt;Conditional job&lt;/h2&gt;
&lt;p&gt;We can run the &lt;code class=&quot;language-text&quot;&gt;publish_package&lt;/code&gt; job conditionally, say only if the merge is to master then only we publish the package by adding &lt;code class=&quot;language-text&quot;&gt;if: github.ref == &apos;refs/heads/master&apos;&lt;/code&gt; before steps as below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;  publish_package:
    name: Publish to nuget.org
    needs: build
    runs-on: windows-latest

    if: github.ref == &apos;refs/heads/master&apos;
    steps:&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Final action yml&lt;/h1&gt;
&lt;p&gt;Let’s put all these together into one final workflow yml. The objective being, we build and test for a push to any branch or PR to master. But we publish only on push to master.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;name: ADOSCloneAllRepos CI-CD

on:
  push:
    branches: [ &apos;*&apos; ]
  pull_request:
    branches: [ master ]

jobs:
  build:
    name: Build and test
    runs-on: windows-latest
    # Map a step output to a job output
    outputs:
      semVer: ${{ steps.gitversion.outputs.semVer }}

    steps:
    - uses: actions/checkout@v2
    - name: Fetch all history for all tags and branches
      run: git fetch --prune --unshallow
    - name: Install GitVersion
      uses: gittools/actions/gitversion/setup@v0.9.2
      with:
          versionSpec: &apos;5.2.x&apos;
    - name: Use GitVersion
      id: gitversion # step id used as reference for output values
      uses: gittools/actions/gitversion/execute@v0.9.2
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.101
    - name: Install dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore -p:Version=${{ steps.gitversion.outputs.semVer }}
    - name: Test
      run: dotnet test --no-restore --verbosity normal
    - name: Upload ADOSCloneAllRepos
      uses: actions/upload-artifact@v2
      with:
        name: ADOSCloneAllRepos
        path: ADOSCloneAllRepos\bin\Release\ADOSCloneAllRepos.${{ steps.gitversion.outputs.semVer }}.nupkg

  publish_package:
    name: Publish to nuget.org
    needs: build
    runs-on: windows-latest

    if: github.ref == &apos;refs/heads/master&apos;
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v1
        with:
          name: ADOSCloneAllRepos
      - name: Setup .NET Core
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 3.1.101
      - name: Push package to nuget.org
        run: dotnet nuget push ./ADOSCloneAllRepos/ADOSCloneAllRepos.${{needs.build.outputs.semVer}}.nupkg -k ${{ secrets.NUGET_ORG_API_KEY }} -s https://api.nuget.org/v3/index.json
      - name: Tag commit
        uses: tvdias/github-tagger@v0.0.1
        with:
          repo-token: &quot;${{ secrets.GITHUB_TOKEN }}&quot;
          tag: &quot;${{needs.build.outputs.semVer}}&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice the mapping of job output at build, where it maps GitVersion output to a variable called &lt;code class=&quot;language-text&quot;&gt;semVer&lt;/code&gt; which can then be accessed at the next job.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;build:
    name: Build and test
    runs-on: windows-latest
    # Map a step output to a job output
    outputs:
      semVer: ${{ steps.gitversion.outputs.semVer }}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;GitHub Actions are really powerful and make automation easy on GitHub (also on GitHub Enterprise). For full code check &lt;a href=&quot;https://github.com/realrubberduckdev/azure-devops-clone-all-repos&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;azure-devops-clone-all-repos&lt;/a&gt;. &lt;a href=&quot;https://help.github.com/en/actions&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GitHub Actions documentation&lt;/a&gt; is a great starting point as well. Special thanks to &lt;a href=&quot;https://twitter.com/Timboski&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Timboski&lt;/a&gt; for helping with the project.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Azure pipelines - security & compliance using templates]]></title><description><![CDATA[Templates in Azure pipelines Templates are a great way to achieve what we could do using Task groups for builds and releases in classic…]]></description><link>https://rubberduckdev.com/templates-security/</link><guid isPermaLink="false">https://rubberduckdev.com/templates-security/</guid><pubDate>Tue, 26 May 2020 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Templates in Azure pipelines&lt;/h1&gt;
&lt;p&gt;Templates are a great way to achieve what we could do using &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/library/task-groups?view=azure-devops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Task groups for builds and releases&lt;/a&gt; in classic Azure DevOps pipelines. Using templates we can define reusable content, logic, and parameters.&lt;/p&gt;
&lt;h1&gt;Template types&lt;/h1&gt;
&lt;p&gt;There are two types of templates, classified based on their usage.&lt;/p&gt;
&lt;h2&gt;Type 1: Include/insert template&lt;/h2&gt;
&lt;p&gt;The include/insert type templates can be used to include content, similar to include directive in many programming languages. Or in the lines of XML include, where the content of one file can be inserted into another.&lt;/p&gt;
&lt;h2&gt;Type 2: Extend template&lt;/h2&gt;
&lt;p&gt;The extends templates provide an outer structure of the pipeline and a set of places where the template consumer can make targeted alterations. Think in the lines of inheriting from an abstract class in C#. This kind of extends templates can be used to control what is allowed in a pipeline, the template defines logic that another file must follow.&lt;/p&gt;
&lt;h1&gt;Enforcing policy - security &amp;#x26; compliance&lt;/h1&gt;
&lt;p&gt;Using the extends templates, we can enforce policies on agent pools or environments.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;A simple usage can be as follows. The &lt;code class=&quot;language-text&quot;&gt;start.yml&lt;/code&gt; file below is the template which can be extended.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# File: start.yml
parameters:
- name: buildSteps # the name of the parameter is buildSteps
  type: stepList # data type is StepList
  default: [] # default value of buildSteps
stages:
- stage: secure_buildstage
  pool: Hosted VS2017
  jobs:
  - job: secure_buildjob
    steps:
    - script: echo This happens before code 
      displayName: &apos;Base: Pre-build&apos;
    - script: echo Building
      displayName: &apos;Base: Build&apos;

    - ${{ each step in parameters.buildSteps }}:
      - ${{ each pair in step }}:
          ${{ if ne(pair.value, &apos;CmdLine@2&apos;) }}:
            ${{ pair.key }}: ${{ pair.value }}
          ${{ if eq(pair.value, &apos;CmdLine@2&apos;) }}:
            &apos;${{ pair.value }}&apos;: error

    - script: echo This happens after code
      displayName: &apos;Base: Signing&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This file takes in the build steps as a parameter and runs a &lt;code class=&quot;language-text&quot;&gt;secure_buildstage&lt;/code&gt;. As part of that stage, it is doing simple display statements here, but the idea is that it could be some very specific build steps it can perform, viz. build with code signing.&lt;/p&gt;
&lt;p&gt;Some steps are generated using &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;template expressions&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;${{ if eq(pair.value, &apos;CmdLine@2&apos;) }}:
            &apos;${{ pair.value }}&apos;: error&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above expression, for example, says if we try to add a (CmdLine@2 task)[https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/command-line?view=azure-devops&amp;#x26;tabs=yaml] then the pipeline will throw an error, essentially fail to build. This could be any other security or compliance requirement we might want to enforce on the pipeline.&lt;/p&gt;
&lt;p&gt;The way to use the extends template is from a pipeline YAML file use extends key.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# File: azure-pipelines.yml
trigger:
- master

extends:
  template: start.yml
  parameters:
    buildSteps:  
      - bash: echo Test #Passes
        displayName: succeed
      - bash: echo &quot;Test&quot;
        displayName: succeed
      - task: CmdLine@2
        displayName: Test 3 - Will Fail
        inputs:
          script: echo &quot;Script Test&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Approval and checks&lt;/h2&gt;
&lt;p&gt;So until now, we have discussed how to enforce a policy after we have extended a template. The important bit is how to enforce that extension. This can be done in two places.&lt;/p&gt;
&lt;h3&gt;Environment&lt;/h3&gt;
&lt;p&gt;In Azure pipelines &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/environments?view=azure-devops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;deployment environments&lt;/a&gt; we can enable checks that any pipeline deploying to that environment must have extended a specific template.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 14.334470989761092%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAe0lEQVQI143Puw7CMAyF4TxK41TkYqc3BKVDCSBVQmLi/R/mIGdgKhLDt9i/B5u+y1DCCcEfqhQDWkcg2/yke20dWbTUwPIRblphSrliucwYh/4b1sjRX7Tl6DGUF9LtDSNZEIMHp4gsvEs/2Jt3WerdfD7huT1wXxd8AEUYWQwCAka0AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;environment-approval-checks&quot;
        title=&quot;environment-approval-checks&quot;
        src=&quot;/static/7f7ecb45c70f7aba1154aa430a0d7d14/463a7/environment-approval-checks.png&quot;
        srcset=&quot;/static/7f7ecb45c70f7aba1154aa430a0d7d14/8890b/environment-approval-checks.png 293w,
/static/7f7ecb45c70f7aba1154aa430a0d7d14/1f316/environment-approval-checks.png 585w,
/static/7f7ecb45c70f7aba1154aa430a0d7d14/463a7/environment-approval-checks.png 1170w,
/static/7f7ecb45c70f7aba1154aa430a0d7d14/79227/environment-approval-checks.png 1607w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
    &lt;/span&gt;
&lt;/br&gt;&lt;/p&gt;
&lt;h3&gt;Agent pool&lt;/h3&gt;
&lt;p&gt;In Azure pipelines &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/pools-queues?view=azure-devops&amp;#x26;tabs=yaml%2Cbrowser&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;agent pools&lt;/a&gt; we can enable checks that any pipeline running on those agents will need to have extended a specific template.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.51877133105802%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAA7DAAAOwwHHb6hkAAABA0lEQVQoz42RWXKEMAxE5yZZYPYAXsE2wwCTVO5/o061amySv3yoypKt1y15F4Ye59MRRissy4y2aWCNxr6uJA77WuL3OedSqyvErsU89Ji8w051Ld5eXwS4rgusM7Baw1oDrTp0bQPvrNwzYghS772TnOK90QjOQjUf2LGxrt7l0fq54P6YcEsJKQa4JzSGQaDWGIwpCiilCE4nQkbDe4eWQGctTsdDAY73JE0EUp0TsJFw5qwTwhrz4jIDqXy9nKG7DcgmuuJDCv0HSE4BcrlGKQHe5hFjjAVIh9vIehv5uRLW/jj8ThHqekEYBjy+VqQplpHzDofel+a8t+w6f1Z2+AOXed6fCkb5IQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;agentpool-approval-checks&quot;
        title=&quot;agentpool-approval-checks&quot;
        src=&quot;/static/e6905aadb5211537a527985c5d6dfbb8/463a7/agentpool-approval-checks.png&quot;
        srcset=&quot;/static/e6905aadb5211537a527985c5d6dfbb8/8890b/agentpool-approval-checks.png 293w,
/static/e6905aadb5211537a527985c5d6dfbb8/1f316/agentpool-approval-checks.png 585w,
/static/e6905aadb5211537a527985c5d6dfbb8/463a7/agentpool-approval-checks.png 1170w,
/static/e6905aadb5211537a527985c5d6dfbb8/e1fbe/agentpool-approval-checks.png 1755w,
/static/e6905aadb5211537a527985c5d6dfbb8/68991/agentpool-approval-checks.png 1830w&quot;
        sizes=&quot;(max-width: 1170px) 100vw, 1170px&quot;
      /&gt;
    &lt;/span&gt;
&lt;/br&gt;
&lt;/br&gt;
On any of those two settings, environment or agent pool, we can enable template check.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 461px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 105.46075085324233%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAA7CAAAOwgEVKEqAAAAC6ElEQVQ4y41U6W7TQBj0u4DElcNXfCQ+YseOYztxDidx2gokkDgECCEBD4D6IjwIEo82MF/rUKUF9cfo21155ztmvMqh2ePjh/c4NA3evnmNbJqi3+vC0DXomnovqP0exmEAx7agDF0Hge+BMQx8DExDCPnRfaCpfYm8xyIULkhGuI4NbzSE740kcs+s94UQjschVssK23qDzXqF3XaL3bZGXW8QjUNp5T7gt9bAhNLOQFOvWuh1OwKeM+P/cNc8FUPXYVsDAVuMozHiKJIL3c4z9K8TEJztzTXndwqFvSeTGIt5iWma4OzQYLVcIk0SOWeCyXVkW5M4knMKSYJbFbJNVsKsFIhzMA1dhtwK1YpGtKLxvFW5hRDy8izLRBR+uKwWmJcFyiKXqhnLsoDve0LAS7zMkbAA3jeNK8tIyzzkzOrNRnxIEpKSaFlVQl7kMxRFjjSZiPHbyFHMsimm01SU5lyVq1L7MLSeZGHG0/jXCbdFaCs+tqyqGr59fYdfP38gCCIc9jvxI0Fxmv1exGD2u2xyUxhpmZmTeIyLpoY1GIh6N/8WqmtZgzsVvYtcMXUVuu2g58WwLVOIWueTvPXc6f972urRNu7AQLjcIf38HSkrvTjHfrcFX6HVann0If1HzGYZPG90VPy0dRGl8/QJHj18cGsmBH3ZwrauHgEK9S9xFF3T0By2uLz8Bs/zpKri2nubzVoiwco0XmIi/st0wTUkOYlZIRfrRYkvn97Ccx2s5iWKaYo8TVAVuWCez7BgkixDRu8lkyN4xqrbESiVqiLudhA+foLSNJDrGgpdR65psp/2eph0niHpdpB2u4Kk05G9nPW6mKkqBm3LzjCHlZVY12tMywJnL56jubjA7vwMz1+9RL6Yww18DAMfDl/2MEA4ieFFYwRxBMMbIdI1+JqKPglrVcPMNLF2Halo5diC2huhsi1shq6s10MXa9eV/db3UI+GaMIAlW2j6vcx1DWofwh/Ax0OxGWTIVdXAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;template-checks&quot;
        title=&quot;template-checks&quot;
        src=&quot;/static/ee0bb576de4c077c492447a705372d53/1e015/template-check.png&quot;
        srcset=&quot;/static/ee0bb576de4c077c492447a705372d53/8890b/template-check.png 293w,
/static/ee0bb576de4c077c492447a705372d53/1e015/template-check.png 461w&quot;
        sizes=&quot;(max-width: 461px) 100vw, 461px&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;Issues, tips &amp;#x26; tricks&lt;/h1&gt;
&lt;p&gt;Templates are really useful to enforce security and compliance requirements as described above. Although it does create a few issues. Luckily we seem to have solutions or at least workarounds for these.&lt;/p&gt;
&lt;h2&gt;Complexity&lt;/h2&gt;
&lt;p&gt;Using templates can mean many seemingly unrelated files are related. The overall pipeline with logic, expressions and parameters can grow very quickly. That adds complexity to the system. The current solution provided by Microsoft are limits set on them. You can find &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops#limits&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;details of the limits in documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Breaking changes&lt;/h2&gt;
&lt;p&gt;Along with complexity, there is the issue of introducing breaking changes in templates. If a template is used across pipelines and we want to introduce a breaking change, say for a new pipeline, it can still break the older ones. This can be avoided by using Git branch or tag. For example, keep the template in a separate repo as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# template.yml
parameters:
- name: usersteps
  type: stepList
  default: []
steps:
- ${{ each step in parameters.usersteps }}
  - ${{ step }}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And use the template by specifying the &lt;code class=&quot;language-text&quot;&gt;type&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;repository&lt;/code&gt; &amp;#x26; &lt;code class=&quot;language-text&quot;&gt;ref&lt;/code&gt;. This locks it down to a specific revision.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# azure-pipelines.yml
resources:
  repositories:
  - repository: templates
    type: git
    name: MyProject/MyTemplates
    ref: tags/v1

extends:
  template: template.yml@templates
  parameters:
    usersteps:
    - script: echo This is my first step
    - script: echo This is my second step&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;As we have seen till now, templates are a great way of reusing pipeline code. As a bonus, they are now super useful for enforcing security and compliance practices on our software team. I hope this was useful, please do share any thoughts or comments you might have.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Automated webapp deployment - Git to Azure]]></title><description><![CDATA[GitOps Have you heard of GitOps yet? I have come across this term on multiple Software Engineering Daily podcasts. And it is amazing how it…]]></description><link>https://rubberduckdev.com/azure-github-webapp-deployment/</link><guid isPermaLink="false">https://rubberduckdev.com/azure-github-webapp-deployment/</guid><pubDate>Tue, 05 May 2020 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;GitOps&lt;/h1&gt;
&lt;p&gt;Have you heard of GitOps yet? I have come across this term on multiple &lt;a href=&quot;https://softwareengineeringdaily.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Software Engineering Daily&lt;/a&gt; podcasts. And it is amazing how it fits into the idea of robust infrastructure and seamless deployment with infrastructure as code. One of the main essence of GitOps, as far as I understand and also what &lt;a href=&quot;https://www.atlassian.com/git/tutorials/gitops&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Atlassian&lt;/a&gt; has emphasized is that Git effectively can become one “source of truth”. Meaning for both code and infrastructure. I like this idea and point of this post is about reflecting Git changes on web deployments.&lt;/p&gt;
&lt;h1&gt;Deploying web app from GitHub to Azure&lt;/h1&gt;
&lt;h2&gt;Pre-requisites&lt;/h2&gt;
&lt;p&gt;This post assumes that you have at least beginner level knowledge regarding Git, GitHub, Azure, Azure DevOps &amp;#x26; Powershell.&lt;/p&gt;
&lt;h2&gt;Deploying GitHub source to web&lt;/h2&gt;
&lt;p&gt;The steps are nicely described in &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/deploy-continuous-deployment#authorize-azure-app-service&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft documentation - Authorize Azure App Service&lt;/a&gt;. We need to &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/deploy-continuous-deployment#prepare-your-repository&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;prepare the repository&lt;/a&gt; with right files at the root for the Azure app service to perform the deployment.&lt;/p&gt;
&lt;p&gt;This is what the following &lt;a href=&quot;https://github.com/realrubberduckdev/app-service-web-dotnet-get-started/blob/3472f962a6838b03b939d60a1c514598cfe7d82f/DeploymentScripts/GitHubDeployment.ps1&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;powershell script&lt;/a&gt; does using &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/azure/install-az-ps?view=azps-3.8.0&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure PowerShell&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[CmdletBinding()]
Param(
    [Parameter()]
    [string]$resourceGroupName = &quot;web-deployment-test-rg&quot;,
    [Parameter()]
    [string]$webappname = &quot;webapp-github-deployment-&quot; + $(Get-Random)
)

# Replace the following URL with a public GitHub repo URL
$gitrepo=&quot;https://github.com/realrubberduckdev/app-service-web-dotnet-get-started.git&quot;
$location=&quot;UK South&quot;

# Connect to Azure subscription
Connect-AzAccount

Get-AzResourceGroup -Name $resourceGroupName -ErrorVariable notPresent -ErrorAction SilentlyContinue
if ($notPresent)
{
    # Create a resource group.
    New-AzResourceGroup -Name $resourceGroupName -Location $location
}

# Create an App Service plan in Free tier.
New-AzAppServicePlan -Name $webappname -Location $location `
-ResourceGroupName $resourceGroupName -Tier Free

# Create a web app.
New-AzWebApp -Name $webappname -Location $location `
-AppServicePlan $webappname -ResourceGroupName $resourceGroupName

# Configure GitHub deployment to the staging slot from your GitHub repo and deploy once.
$PropertiesObject = @{
    repoUrl = &quot;$gitrepo&quot;;
    branch = &quot;master&quot;;
}
Set-AzResource -PropertyObject $PropertiesObject -ResourceGroupName $resourceGroupName `
-ResourceType Microsoft.Web/sites/SourceControls `
-ResourceName $webappname/web -ApiVersion 2019-08-01 -Force&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Essentially the script does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Securely connect to Azure&lt;/li&gt;
&lt;li&gt;Create resource group if needed&lt;/li&gt;
&lt;li&gt;Create Azure app service&lt;/li&gt;
&lt;li&gt;Create Azure web app&lt;/li&gt;
&lt;li&gt;Set web app deployment to Github repo master branch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is just one-time deployment as you may see the master branch is always synced with deployment. Any commits and changes to the web app will be deployed upon merge/push to master.
Hence it is a continuous deployment model.&lt;/p&gt;
&lt;h2&gt;Blue/Green deployment from Github&lt;/h2&gt;
&lt;p&gt;This is the staging &amp;#x26; product slots based deployment. The &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;setup steps are described in Microsoft documentation&lt;/a&gt;. Although it is even easier to deploy using our &lt;a href=&quot;https://github.com/realrubberduckdev/app-service-web-dotnet-get-started/blob/3472f962a6838b03b939d60a1c514598cfe7d82f/DeploymentScripts/GitHubDeploymentWithSlots.ps1&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;powershell script&lt;/a&gt; below.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[CmdletBinding()]
Param(
    [Parameter()]
    [string]$resourceGroupName = &quot;web-deployment-test-rg&quot;,
    [Parameter()]
    [string]$webappname = &quot;webapp-github-deployment-with-slots-&quot; + $(Get-Random)
)

# Replace the following URL with a public GitHub repo URL
$gitrepo=&quot;https://github.com/realrubberduckdev/app-service-web-dotnet-get-started.git&quot;
$location=&quot;UK South&quot;

# Connect to Azure subscription
Connect-AzAccount

Get-AzResourceGroup -Name $resourceGroupName -ErrorVariable notPresent -ErrorAction SilentlyContinue
if ($notPresent)
{
    # Create a resource group.
    New-AzResourceGroup -Name $resourceGroupName -Location $location
}

# Create an App Service plan in Free tier.
New-AzAppServicePlan -Name $webappname -Location $location `
-ResourceGroupName $resourceGroupName -Tier Free

# Create a web app.
New-AzWebApp -Name $webappname -Location $location `
-AppServicePlan $webappname -ResourceGroupName $resourceGroupName

# Upgrade App Service plan to Standard tier (minimum required by deployment slots)
Set-AzAppServicePlan -Name $webappname -ResourceGroupName $resourceGroupName `
-Tier Standard

#Create a deployment slot with the name &quot;staging&quot;.
New-AzWebAppSlot -Name $webappname -ResourceGroupName $resourceGroupName `
-Slot staging

# Configure GitHub deployment to the staging slot from your GitHub repo and deploy once.
$PropertiesObject = @{
    repoUrl = &quot;$gitrepo&quot;;
    branch = &quot;master&quot;;
}
Set-AzResource -PropertyObject $PropertiesObject -ResourceGroupName $resourceGroupName `
-ResourceType Microsoft.Web/sites/slots/sourcecontrols `
-ResourceName $webappname/staging/web -ApiVersion 2019-08-01 -Force

# Swap the verified/warmed up staging slot into production.
Switch-AzWebAppSlot -Name $webappname -ResourceGroupName $resourceGroupName `
-SourceSlotName staging -DestinationSlotName production&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Essentially the script does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Securely connect to Azure&lt;/li&gt;
&lt;li&gt;Create a resource group if needed&lt;/li&gt;
&lt;li&gt;Create Azure app service&lt;/li&gt;
&lt;li&gt;Create an Azure web app&lt;/li&gt;
&lt;li&gt;Create web deployment slots&lt;/li&gt;
&lt;li&gt;Set web app staging slot deployment to Github repo master branch&lt;/li&gt;
&lt;li&gt;Finally switch the slots so the current state of master becomes available in the production slot&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So now that deployment is done, you can go ahead make a change in master. Did you notice that your change is not reflected in the web pages? Let me explain why. The script configures the staging slot to be synced with master and not the production. That is why the script swaps slots at the end. Meaning we need to do the same when master gets new changes and this is where we can do magic using &lt;a href=&quot;https://azure.microsoft.com/en-gb/services/devops/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure DevOps&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Azure Pipelines CI-CD&lt;/h3&gt;
&lt;p&gt;Azure pipelines YAML based pipeline with multiple stages is what we need. The &lt;a href=&quot;https://github.com/realrubberduckdev/app-service-web-dotnet-get-started/blob/3472f962a6838b03b939d60a1c514598cfe7d82f/swap-slots.yml&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;yaml&lt;/a&gt; below &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;triggers on a change to master&lt;/li&gt;
&lt;li&gt;does a ‘dummy build &amp;#x26; test’ as an example, you should do your proper build, followed by another unit testing phase&lt;/li&gt;
&lt;li&gt;because staging slot is already synced with the master branch, we do not need a specific stage for that&lt;/li&gt;
&lt;li&gt;the final deploy to production is a gated stage and needs &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/environments?view=azure-devops#approvals&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;approval&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;trigger:
- master

pool:
  vmImage: &apos;windows-latest&apos;

variables:
  solution: &apos;**/*.sln&apos;
  buildPlatform: &apos;Any CPU&apos;
  buildConfiguration: &apos;Release&apos;

stages:
- stage: Build
  jobs:
  - job: Build
    pool:
      vmImage: &apos;windows-latest&apos;
    steps:
    - script: echo &quot;All build and test pass.&quot;
- stage: deploy_to_production
  dependsOn: Build
  jobs:
  - deployment: deploy_to_production
    pool:
      vmImage: &apos;windows-latest&apos;
    environment: &apos;deploy_to_production&apos;
    strategy:
      runOnce:
        deploy:
          steps:
            - task: AzureAppServiceManage@0
              displayName: AzureAppServiceManage - Swap slots
              inputs:
                azureSubscription: &apos;Visual Studio Enterprise (77be59ef-9321-4911-8e68-dde3b63a9e67)&apos;
                Action: &apos;Swap Slots&apos;
                WebAppName: &apos;webapp-github-deployment-with-slots-1439770693&apos;
                ResourceGroupName: &apos;web-deployment-test-rg&apos;
                SourceSlot: &apos;staging&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This YAML file stays in the git repo and triggers Azure pipelines upon change to master. Team members can test the changes on the staging URL before approving, thereby swapping the slots and deploying changes to production.&lt;/p&gt;
&lt;h2&gt;Deployment using docker&lt;/h2&gt;
&lt;p&gt;Another method of automated deployment is using docker settings on Azure app service. For this, we will need a docker image, as shown below. This is the &lt;a href=&quot;https://github.com/realrubberduckdev/dp-blog/blob/7cad56a0034efbf0d39f0be0ba8470c281674e97/Dockerfile&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;docker file&lt;/a&gt; used to deploy this blogging website.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;FROM node:12.11.1 AS builder
WORKDIR /dpblog
RUN npm i -g gatsby-cli
COPY package*.json ./
RUN npm install
COPY . .
RUN gatsby build

FROM gatsbyjs/gatsby:latest
COPY --from=builder /dpblog/public/ /pub&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a multi-stage docker file and the first builder stage is used to build the static &lt;a href=&quot;https://www.gatsbyjs.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;gatsby&lt;/a&gt; website. Followed by the second stage, where it gets the latest gatsby docker image, which essentially has &lt;a href=&quot;https://www.nginx.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;nginx&lt;/a&gt; and hosts the static website by copying it across from the builder.&lt;/p&gt;
&lt;p&gt;This will need Azure pipelines again to do the build and deployment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/docker-diagram-efb9920365b2db9fb78412da67d197f0.png&quot; alt=&quot;architecture diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;As the architecture diagram shows, we will need to build a new image with the new static contents. Azure pipelines can build this and push to azure container registry. Then either after validation or approval gates or automated deployment, Azure pipelines can then update the Azure app service docker settings to pick up the new docker image.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Azure, Azure pipelines, Github work seamlessly in these scenarios of web deployment. We can keep all relevant info from code, infrastructure creation to pipeline and more on Git. Making it one “source of truth”.&lt;/p&gt;
&lt;h2&gt;Github&lt;/h2&gt;
&lt;p&gt;For full code visit &lt;a href=&quot;https://github.com/realrubberduckdev/app-service-web-dotnet-get-started&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;realrubberduckdev/app-service-web-dotnet-get-started&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Convert MediaWiki to Markdown wiki]]></title><description><![CDATA[Introduction MediaWiki and MD based Wikis MediaWiki is a popular wiki. It powers Wikipedia as well as used by thousands of other companies…]]></description><link>https://rubberduckdev.com/mediawiki-to-md/</link><guid isPermaLink="false">https://rubberduckdev.com/mediawiki-to-md/</guid><pubDate>Sat, 18 Apr 2020 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;h2&gt;MediaWiki and MD based Wikis&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mediawiki.org/wiki/MediaWiki&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;MediaWiki&lt;/a&gt; is a popular wiki. It powers Wikipedia as well as used by thousands of other companies and organizations. Although it is quite powerful, extensible, customizable and reliable, the markdown based wikis are equally useful, if not more. The frequent updates and &lt;a href=&quot;https://devblogs.microsoft.com/devops/category/wiki/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;improvements of Azure DevOps wiki&lt;/a&gt; is making it a strong contender. The major difference between them will be that ADOS wiki is MD based and MediaWiki is not. This is when we will need to convert MediaWiki articles to MD files.&lt;/p&gt;
&lt;h1&gt;Converting MediaWiki to Markdown&lt;/h1&gt;
&lt;h2&gt;Export MediaWiki Files to XML&lt;/h2&gt;
&lt;p&gt;As the first step, we will need to export MediaWiki content to a single XML file. There are multiple ways of doing this.&lt;/p&gt;
&lt;h3&gt;Option 1: Export content&lt;/h3&gt;
&lt;p&gt;The simplest one is described at &lt;a href=&quot;https://en.wikipedia.org/wiki/Help:Export&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Wikipedia Help:Export&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MediaWiki -&gt; Special Pages -&gt; ‘All Pages’&lt;/li&gt;
&lt;li&gt;With help from the filter tool at the top of ‘All Pages’, copy the page names to convert into a text file (one filename per line).&lt;/li&gt;
&lt;li&gt;MediaWiki -&gt; Special Pages -&gt; ‘Export’&lt;/li&gt;
&lt;li&gt;Paste the list of pages into the Export field. &lt;/li&gt;
&lt;li&gt;Check: ‘Include only the current revision, not the full history’&lt;br&gt;
Note: This convert script will only do the latest version, not revisions. &lt;/li&gt;
&lt;li&gt;Uncheck: Include Templates&lt;/li&gt;
&lt;li&gt;Check: Save as file&lt;/li&gt;
&lt;li&gt;Click on the ‘Export’ button.&lt;/li&gt;
&lt;li&gt;An XML file will be saved locally.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s call this exported file &lt;code class=&quot;language-text&quot;&gt;mediawiki_dump.xml&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Option 2: Dump backup&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mediawiki.org/wiki/Manual:dumpBackup.php&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;MediaWiki manual&lt;/a&gt; shows how to use the &lt;code class=&quot;language-text&quot;&gt;dumpBackup.php&lt;/code&gt; script.&lt;/p&gt;
&lt;p&gt;Steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log into your mediawiki instance&lt;/li&gt;
&lt;li&gt;Find the PHP file …/maintenance/dumpBackup.php and your ../LocalSettings.php file. Then try:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;php .../maintenance/dumpBackup.php --conf .../LocalSettings.php --full &gt; mediawiki_dump.xml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note we can use parameters —include-files —uploads to ensure the exported XML includes all the images and other files. But in our example, this currently doesn’t work.&lt;/p&gt;
&lt;h2&gt;Convert exported XML to MD&lt;/h2&gt;
&lt;p&gt;For conversion, we will need to use a PHP script, use &lt;a href=&quot;https://pandoc.org/index.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Pandoc&lt;/a&gt; and we also need &lt;a href=&quot;https://getcomposer.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Composer&lt;/a&gt; for the script environment setup. That is quite a lot of dependencies for us to install and this will be operating system specific, so that adds even more variables to the mix. So what do we do?&lt;/p&gt;
&lt;p&gt;You guessed it right, and sure enough, we use docker. The docker file below is from &lt;a href=&quot;https://github.com/realrubberduckdev/mediawiki-to-markdown/blob/42e4b1f6c8b32ddbb45b5ddcd088ad9912b20004/Dockerfile&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;mediawiki-to-markdown/Dockerfile&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;FROM pandoc/latex
WORKDIR /src
COPY composer.* ./

# Install composer, refer: https://github.com/geshan/docker-php-composer-alpine/blob/master/Dockerfile
RUN apk --update add wget \ 
             curl \
             git \
             php7 \
             php7-curl \
             php7-openssl \
             php7-iconv \
             php7-json \
             php7-mbstring \
             php7-phar \
             php7-xml \
             php7-simplexml \
             php7-dom --repository http://nl.alpinelinux.org/alpine/edge/testing/ &amp;amp;&amp;amp; rm /var/cache/apk/*

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer 
# end of install composer

RUN composer install
COPY . .
CMD [&quot;sh&quot;]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, this docker files gets &lt;a href=&quot;https://hub.docker.com/r/pandoc/latex&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Pandoc docker image&lt;/a&gt;, which already has got PHP from dockerhub. So two of our major dependencies are resolved immediately. Followed by composer, which we install using &lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;apk Alpine Linux package management&lt;/a&gt;. Finally, after the &lt;code class=&quot;language-text&quot;&gt;RUN composer install&lt;/code&gt; step, the container will be ready for processing XML files.&lt;/p&gt;
&lt;p&gt;The steps to do the conversion now are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy the &lt;code class=&quot;language-text&quot;&gt;mediawiki_dump.xml&lt;/code&gt; to the same folder as the docker file.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build the image using the following command: &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;docker build -t MediaWiki2MD .&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After building the docker image, we run it with a volume mapped for accessing the output files.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;docker run -v ./output/:/src/output MediaWiki2MD sh -c &quot;php convert.php --filename=mediawiki_dump.xml --output=./output&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note: The convert.php file is at &lt;a href=&quot;https://github.com/realrubberduckdev/mediawiki-to-markdown/blob/4ebf945e68984270c820e8fe6a892e0acfc6875d/convert.php&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;mediawiki-to-markdown/convert.php&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This should generate the MD files in the output folder.&lt;/p&gt;
&lt;h2&gt;Limitations&lt;/h2&gt;
&lt;p&gt;As discussed earlier, this process still fails to gather images, pdfs or other uploaded documents. That is something I plan to &lt;a href=&quot;https://en.wikipedia.org/wiki/Secure_copy&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;SCP&lt;/a&gt; directly from MediaWiki server to the MD files location and write a script to edit the MD files references to the files.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Using docker, we have managed to make the conversion process much easier to manage and reproducible. That takes a big chunk of work out of the process. Although it still needs manual intervention in generating the exported XML and also the final bit of sorting out images and uploaded document links in MD files. If you know of an easier or better way, please do share.&lt;/p&gt;
&lt;h2&gt;Github&lt;/h2&gt;
&lt;p&gt;For full code visit &lt;a href=&quot;https://github.com/realrubberduckdev/mediawiki-to-markdown&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;realrubberduckdev/mediawiki-to-markdown&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Simple Twitter bot using Azure Logic Apps]]></title><description><![CDATA[Introduction Azure Logic Apps Azure Logic Apps is a Microsoft cloud service that helps to schedule, automate, and orchestrate tasks…]]></description><link>https://rubberduckdev.com/twitter-bot/</link><guid isPermaLink="false">https://rubberduckdev.com/twitter-bot/</guid><pubDate>Sun, 29 Mar 2020 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;h2&gt;Azure Logic Apps&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/logic-apps/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Logic Apps&lt;/a&gt; is a Microsoft cloud service that helps to schedule, automate, and orchestrate tasks, business processes, and workflows when we need to integrate apps, data, systems, and services across enterprises or organizations.&lt;/p&gt;
&lt;h2&gt;Twitter bot&lt;/h2&gt;
&lt;p&gt;Twitter bots are possibly a thing of the past now, yet can be still useful. A twitter bot can automatically perform tweet/retweet/follow etc actions, either blindly or based on certain conditions. There are many ways to design one, using the host of &lt;a href=&quot;https://developer.twitter.com/en/docs/developer-utilities/twitter-libraries&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;libraries provided by twitter&lt;/a&gt; or the Twitter API itself on &lt;a href=&quot;https://developer.twitter.com/en&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;developer.twitter.com&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Twitter bot on Azure Logic Apps&lt;/h1&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;Let’s say we want a twitter bot that retweets every time I tweet something or if it meets the search criteria by containing keywords as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;#rubberduckdev
#nuget 
#msbuild 
#azure 
azure 
azure devops 
#rubberduckdev&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Development (drag &amp;#x26; drop, few clicks or simple steps)&lt;/h2&gt;
&lt;p&gt;Development of logic apps is as simple as few clicks on the Azure portal. A programming background is useful to understand what is going on and troubleshoot if necessary. But it is not mandatory, well that is how Logic Apps have been marketed.&lt;/p&gt;
&lt;p&gt;The steps to take are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Setup a blank logic app. Follow &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/logic-apps/quickstart-create-first-logic-app-workflow&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft documentation&lt;/a&gt; if unsure how it is done. &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 598px;&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 118.43003412969281%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAEbUlEQVQ4y32Uy2+UVRjG+wf0MpVQtdYyIJ3OpTPffOd89+nc2k7baTu9QEsKgRYplaiokcTgwoiBxEtRWOiCpglBNrUk6NrE4CVxUdCNGGHDwoUxYWNkYViIP3NOO+20QRdP3nfO9+bJO8/znFPXtjvO7r0xdkcM9kQFHQmX5+IuT7bHaXlmLy3PRtn5VBstbRF2Ph0mHN5FLBajMxIhnojT2tpKc3MzO3bs0KiLxzpxXRvbEgjTIPBdzHSK5lAjTY0NhBrraWpSfT2hpkYaGhqor6/fqI1NjYRCoQ3UtbW1EQ6H2RUO096+i2g0imEYpFIp0uk0hpHW1TRNXaWUCCH0jDBNfa766kxdZ2cnjuNgWZZGEPhkczlyGlkymQzd3d26BkFAT08P+XyeXDZLPpejWCjo39W5ukgkoomEtDATMfpeOs2x66sc+vQr8pOHGR0apNTfT6VSYWx8HD8IcIWg9dVTNH2yRMPZ9wnKQ4yNjFAeGlrb0LZtpGUj4p30vXmeS7/CmR/ukz18ghPPz3J8fp7p6WmOHDlCX6mEJ0ya33mX5HerhK4s44xUmJuZYf/k1CahsNSGcQZePs3Ra99z6MqX9Bw4zGBfL2PjE/i+r7XzfJ+M49D+2ilCHy/S8t550vk8UulrWTWE0sJKRRFTZ0i+dYfkGzfxR+aoDJUYGh6hWCzieR5BkCFwbfZMv03b69dpOXaJ3MAow+VBSqX+TUJT2jipvURnLjO8AplFiA6cpDfrUCj24rqudlJIiWuZtJ+4zLV7ID64ieyf5JUXX2D26NHaDW3sVITUoYskFx6SOHsfo38OacTWtrcsPec4Lr5j0T7zEdaHP/LEyc/xS6Mc2L+PyuhYDaEQSAUnwPR7EX4PrhfozbbDti2EmyFm5zD9HJbt6Ayq7TWhtU6o4NgWgefgORZGKrUWWsPYWpUBwsQWaaSZXltmPfA1f1mSSqcp9Payb2qK0Yl9us8VCnSrIKuqglws6hALIbUMyllFVsXGho4QFISg5PuUu7s1hnM5hrJZBjMZXRV6CgVNqK5mMpkkmexaM0uIzasnbJu8adJrWeR9n7zrUvA8ysUiA/m8rn2qOo4+V5ldWlri9k+/8PPtuywsLOiclkqlTcKcaZKxbWzfx1ACWxbl4WGNkUqFUrlMXkqyOmKSb77+lgcPf+OPv+7x2fIKBw8eZH5+fiuhGnZ9f81tKbUJVWh91wmVq8sryzz48w6/3/2CCxcvUKmMMjU19XjCWtdUVeKnlcY1G964cYN/Hj2Cv+Hq1at0dHRsuvxfhErs2dlZjs/N0WUYG4TK2cXFRVZXV7l16xbnzp0jkUjotGwxRQ072wjHJyaYnJzUhEUp6bYs/EwGS0q6urpIdnXpbG7JobRtPNNkQBnhefSp+EhJn5QU02kN1aszRxEGAb7nbchRK8/m4yCE/lh9pmQV1dCu3yTVq5dbvTy1Wlex5S5vENYkfzvUjAq2t77hdmwhVFCXX5H+H9SMInzct38Bl1pH2vk0UncAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Blank logic app&quot;
        title=&quot;Blank logic app&quot;
        src=&quot;/static/e219a4c1570317f32df6af6ff4ae9c96/89d5f/blank-app.png&quot;
        srcset=&quot;/static/e219a4c1570317f32df6af6ff4ae9c96/8890b/blank-app.png 293w,
/static/e219a4c1570317f32df6af6ff4ae9c96/1f316/blank-app.png 585w,
/static/e219a4c1570317f32df6af6ff4ae9c96/89d5f/blank-app.png 598w&quot;
        sizes=&quot;(max-width: 598px) 100vw, 598px&quot;
      /&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For logic app trigger, search for twitter and use &lt;code class=&quot;language-text&quot;&gt;When a new tweet is posted&lt;/code&gt;. &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1046px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/165547bd56d822541862ee0e5cc21b96/5eb79/tweet-posted-trigger.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.774744027303754%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAA7DAAAOwwHHb6hkAAABsElEQVQoz4WSXY8SMRRA+QmwbGBnEYZF/CQw/Zh2OszQAQmrq3Ff9GVj4pvG+O6Dv/6YjrAOKvpwctum9+Te27aePJ3xbLZgIQzDeEr7rMfZeZ/ueY/o4oIoiuh2u7TbbTqdzn9pxaMRg8FlzXA4YjR8wGUUMRpfMYjHjOIx8eQR08fPGV9NmDycEsdxfbff79Pr9Y5ozWYzpJQIIRBS1utESuxiwU4IXixLSqVJlEYqjWgiVX1Wo1OkUrTm8zlpmqK1vkelKS5JuNGaN+s1O1+xWm9Y+Qq/3lDt16tqjXX5niWpMT+FtUSpXwSxlOy05u7zN66rDc4aMudwv2GMIU0N1pp6fSSsK1QKqTWZENwKwbuPX9i6HC2SuiW1H0u4f4iHXGvtn0K1bzskZ9Zi5zNMI/FvBPFJoT6ID8LQVogNsiw72jfPTlYY9qlzbLdbvPeUZVnP6JQwcHKGISZC8DbL+fT1OzebDS+953VRoBvfqznLIPunMCRdG8OH93fc7na88p6tc+TO1RWGF0+Nvc89KWy2rDOLX5VUVcXKe/LlkqIoKYqCfFmQWneUG/7zD0uAeW+vpfccAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Tweet posted trigger&quot;
        title=&quot;Tweet posted trigger&quot;
        src=&quot;/static/165547bd56d822541862ee0e5cc21b96/5eb79/tweet-posted-trigger.png&quot;
        srcset=&quot;/static/165547bd56d822541862ee0e5cc21b96/8890b/tweet-posted-trigger.png 293w,
/static/165547bd56d822541862ee0e5cc21b96/1f316/tweet-posted-trigger.png 585w,
/static/165547bd56d822541862ee0e5cc21b96/5eb79/tweet-posted-trigger.png 1046w&quot;
        sizes=&quot;(max-width: 1046px) 100vw, 1046px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter values for &lt;code class=&quot;language-text&quot;&gt;When a new tweet is posted&lt;/code&gt; dialog box. Notice how &lt;code class=&quot;language-text&quot;&gt;Search text&lt;/code&gt; is set as a query. This is because it is a search query in the underlying logic app. So, to satisfy our requirements from subtopic above, it should be &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;from:@rubberduckdev OR #nuget OR #msbuild OR #azure OR azure OR azure devops OR #rubberduckdev&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 614px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/55c64196cd6f5d6ea93c17aae31c56a3/328b5/tweet-posted-search-text.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 45.3924914675768%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABfUlEQVQoz3WQa27dIBCFvaPaBmOwednge69fyU3kKFGUH1E2kCyhy+hGur5TMU7dKlV/fDoDHDgzZB8/fuLx/Tsu909Ynl5x8/yGcXshNeM9RD+jjivqsPxLXOlcj3cIt49Q5ztkbt4QThcYa1GJGrWUCHHA+TJiOJ2pjsOJ9HS+kKb9tNfHeJx730GaDhkTEpwV4Jwjz7+hKHIsy4Lr9Ypt24iHhw1DjDDGwDkLa3do/am6bSG1Q9a4Ht45OOfJ5L1H13Xo+57qSggwzlGW5V8wUsbYH8oSVWOQmS5iGkc476G1Jtq2RdM0BD1QFBSaAtIk6QH+JaTIc3Cp9w6t0ajrGlLKg3ShqioIIYh1XTFNE5RSFBRCIN/hqTjE7w7necI0z1jXGxo1GdPFupafQYq63tFov0yhVANrDUw3IGtsj87b/d8ID6M1Ou8QQo8YejJzVqLiHJyzg7Q+YOXeodAeSlso7Ugb6yFbC2UcjA9EqkshwYT6P5UEbyx+Ac2uNiJyPSuGAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Tweet posted trigger&quot;
        title=&quot;Tweet posted trigger&quot;
        src=&quot;/static/55c64196cd6f5d6ea93c17aae31c56a3/328b5/tweet-posted-search-text.png&quot;
        srcset=&quot;/static/55c64196cd6f5d6ea93c17aae31c56a3/8890b/tweet-posted-search-text.png 293w,
/static/55c64196cd6f5d6ea93c17aae31c56a3/1f316/tweet-posted-search-text.png 585w,
/static/55c64196cd6f5d6ea93c17aae31c56a3/328b5/tweet-posted-search-text.png 614w&quot;
        sizes=&quot;(max-width: 614px) 100vw, 614px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For logic app action following the trigger, search for twitter and use &lt;code class=&quot;language-text&quot;&gt;Retweet&lt;/code&gt;. &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 627px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/df207269a81553abbab1dc6e8163d6d0/c700b/retweet-action.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.02047781569966%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAA7DAAAOwwHHb6hkAAABqUlEQVQoz5WQy27TQBSG/TSlrPBlxjOeSXyJLyR2bOfSQCUui66KEBsWCLFH3fEAbHjUD40DURelDYtP55zR0X/+f7z3X+542Q0shz3t5kC7uWI1XtGst8zKJfNq9SRur1j2JHaGt7v9SlE1lKueuh2o2p6mGymaFp2WJFmFySuSrHwUW9QobfDMPEdpTawUUsbIOP5TFUpplE4wxh77R9BJghAS7/buF32/peh2yKbH1muMo3J0x3qaHyYpW+rhgJlneH1e0RQV15Hk8Ow5vrZIZRD/SWyzKaW3W5Tsxy1vjeVNGKHm6RRTJeZ8tMGmOcoJ1jNDn86wUtIeXpNXNUHgI4QgiqInEVFEGIZorY6C169ueHfziX1VchCKxA8I3HIYTovn4ISNtag4xvv87Qcfv/9kVRV8uLykubjghZDT5XMcOkEpJVmWETvBxs5Yz1O2RUGdZWSLxeTO9/0HCYPgyD13QRBMcafIXb+mH0e6YWDcbBiGgbquKcuSpmlYLpdT797cnKQLYpuf3EWnP9RHQZc7lhIpxGTd4aw7/l693wsZEwn5T8Hfq42Z/iFEXBgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Tweet posted trigger&quot;
        title=&quot;Tweet posted trigger&quot;
        src=&quot;/static/df207269a81553abbab1dc6e8163d6d0/c700b/retweet-action.png&quot;
        srcset=&quot;/static/df207269a81553abbab1dc6e8163d6d0/8890b/retweet-action.png 293w,
/static/df207269a81553abbab1dc6e8163d6d0/1f316/retweet-action.png 585w,
/static/df207269a81553abbab1dc6e8163d6d0/c700b/retweet-action.png 627w&quot;
        sizes=&quot;(max-width: 627px) 100vw, 627px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;Retweet&lt;/code&gt; action will need to know the tweet id to be retweeted and this will be obtained from the trigger. Search &lt;code class=&quot;language-text&quot;&gt;Tweet id&lt;/code&gt; in dynamic content and select it. &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 962px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/55c7aabfa036e5db096c3cd4bee4e680/21cd7/tweet-id.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.44027303754266%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABy0lEQVQoz12QXWvUQBiF80u8KGjLdpPdyUxmMskks7vJJms/LIht12oVv+4V8V5BhCJSRH+BIgjipVAQf9sjybKIe/Fw5nCGmfO+we2nL5jv3aLZO2RxcETV7pP5inza9PofkxrrPFIqpEp6VJIgYknTthyfnBLsP36Jbw9J/JykrNFFhS5rzHSBLlfnpKhIihl22qBzz3g4YLS7zWjQcaPXwfUttreuEeydPsLPWyohafICX7f4WY3JHCYrek2LCdZXuGlDOmk5fPWZ5eUVpx9+cffyirPLK+5//M35pz8EfWUhsFGENwaTGlKjMXpNQmotNi/IywnazTh5fsGz9z95+PYb5xc/OHv3nTuvv3D85iuBm1SURUFWFLiyxHvfo7VGSolSaqUdOsHFkqUQtHXNovtYjJDjiHCwQ7S7Q6BMhjKWJM1QeqXa5itvLFKnSG174i5PDF4IZllOnWZIpRmOxkTjmHAsCOI4ZpM8z5nP51hre981lHGMUIp0OOS8qDhY3KSxFiclwzAkDEOiKCLoL2/Qjbmm8/E6UwoRDjlePuHB8h5H0yneWnLncM5hjPn34LrJZtvNXIgxtvC4bu9ZRpbn/UQd3d7/Ah1MOANdLOXtAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Tweet posted trigger&quot;
        title=&quot;Tweet posted trigger&quot;
        src=&quot;/static/55c7aabfa036e5db096c3cd4bee4e680/21cd7/tweet-id.png&quot;
        srcset=&quot;/static/55c7aabfa036e5db096c3cd4bee4e680/8890b/tweet-id.png 293w,
/static/55c7aabfa036e5db096c3cd4bee4e680/1f316/tweet-id.png 585w,
/static/55c7aabfa036e5db096c3cd4bee4e680/21cd7/tweet-id.png 962w&quot;
        sizes=&quot;(max-width: 962px) 100vw, 962px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s it. Now save and our Twitter bot is ready. This bot will retweet any tweet by Twitter handle @rubberduckdev or any tweet that contains any of the keywords listed above. Although by design, this will only trigger once per hour (see limitations subtopic) and hence you may not see retweets straightaway. See the logic app run history for a detailed log.&lt;/p&gt;
&lt;h2&gt;Limitations&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.microsoft.com/en-us/connectors/twitter/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Twitter logic app connector documentation page&lt;/a&gt; provides details on the limitations it comes with. One of the main limiting factors is &lt;code class=&quot;language-text&quot;&gt;&quot;Frequency of trigger polls: 1 hour&quot;&lt;/code&gt;, meaning the logic app will trigger only once an hour.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The azure logic app is useful for a whole lot more than a simple twitter bot. But this is a simple example to get to understand how it works. It comes with a host of connectors, easy to use visual workflow and also many default templates to work with which are described in much detail in &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/logic-apps/logic-apps-overview#why-use-logic-apps&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft documentation&lt;/a&gt;.
Our twitter bot aims to do a simple task of retweeting and so the limitation we discussed above is not a major issue. Other connectors can come with their advantages as well as limitations, and we can also create a user voice if we want specific connectors.
With all this in mind, I hope this introduction to logic apps was useful and if you have any comments, thoughts or queries please do comment below or tweet me.&lt;/p&gt;
&lt;h2&gt;Credits&lt;/h2&gt;
&lt;p&gt;Cover image from &lt;a href=&quot;https://www.cmarix.com/wp-content/uploads/2019/10/Azure-Logic-Apps.jpg&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;cmarix&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SDK style csproj & .Net framework]]></title><description><![CDATA[Introduction The sdk style csproj (csharp project style) are becoming the new standard for csproj files. All dotnet core and .netstandard…]]></description><link>https://rubberduckdev.com/sdkstyle-framework-csproj/</link><guid isPermaLink="false">https://rubberduckdev.com/sdkstyle-framework-csproj/</guid><pubDate>Sat, 22 Feb 2020 19:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.microsoft.com/en-us/nuget/resources/check-project-format&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;sdk style csproj (csharp project style)&lt;/a&gt; are becoming the new standard for csproj files. All dotnet core and .netstandard project files are currently using these project styles. Microsoft is also constantly bringing in new improvements for sdk style projects, refer &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/tools/csproj&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Additions to the csproj format for .NET Core&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;SDK style csproj advantages&lt;/h1&gt;
&lt;p&gt;The sdk style csproj files come with many advantages. The main one being they are being constantly supported and improved by Microsoft. The other technical advantages are as follows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Less clutter&lt;/strong&gt;: The csproj no more has to keep a list of files to include, rather can keep a list to exclude. This makes it cleaner and developers do not need to remember to add new files to the csproj. Just placing in the folder of csproj adds it. Thereby less clutter in the csproj file and this reduces noise in git commits too. In addition to this, we do not need any reference to System etc too. Only the project references and package references.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MSBuild /t:restore&lt;/strong&gt;: With sdk style csproj, we can perform a nuget restore using msbuild target ‘restore’. This is great because we will be dependent on one less tool in our build process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generate package on build&lt;/strong&gt;: We can almost eliminate the need for nuspec files to generate nupkg. I say ‘almost’ because there can be a case when we will still need it, described in &lt;a href=&quot;https://www.rubberduckdev.com/msbuild-nuget/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Generate nupkg using msbuild&lt;/a&gt;. In most cases we can just set &lt;code class=&quot;language-text&quot;&gt;&amp;lt;GeneratePackageOnBuild&gt;true&amp;lt;/GeneratePackageOnBuild&gt;&lt;/code&gt; and generate nupkg from csproj.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto reload&lt;/strong&gt;: We can edit the csproj directly in visual studio without having to unload and reload the csproj and then it can auto-reload as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Not for .Net Framework csproj!&lt;/h1&gt;
&lt;p&gt;Although we have established that sdk style csproj is here for the good, it is not the default project template setting on visual studio. Or to be honest, Microsoft does not ship sdk style csproj for .net framework projects.&lt;/p&gt;
&lt;p&gt;There have been many requests in this regard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/50933230/visual-studio-2017-use-new-style-csproj-by-default-when-creating-new-projects&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Visual Studio 2017, use new style csproj by default when creating new projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/58547618/how-do-you-create-a-net-program-using-the-new-csproj-format-in-vs2017-or-vs201&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;How do you create a .NET program using the new CSPROJ format in VS2017 (Or VS2019)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And two from me too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/rubberduckdev/status/1130786137142964224&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Tweet&lt;/a&gt; triggering a discussion on the topic of default project templates.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developercommunity.visualstudio.com/idea/576677/new-style-csproj-as-default-net-framework-project.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Visual studio developer community ticket&lt;/a&gt; to get an answer from Microsoft.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, as it seems, there is no current plan to implement or ship sdk style projects for the .net framework. The possible explanation can be that, the upcoming &lt;a href=&quot;https://devblogs.microsoft.com/dotnet/introducing-net-5/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;.NET 5&lt;/a&gt; is likely to be made a better place to migrate to. Until then we all have a large amount of .net framework code in our repositories.&lt;/p&gt;
&lt;h1&gt;SDK style .net framework project template&lt;/h1&gt;
&lt;p&gt;As a simple solution, we now have sdk style project templates visual studio extension &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=rubberduckdev.SDKStyleTemplatesNet&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;SDKStyle.Templates.Net&lt;/a&gt; in the market place. Hosted on &lt;a href=&quot;https://github.com/realrubberduckdev/SDKStyle.Templates.Net&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The SDKStyle.Templates.Net hopefully will be helpful until we get official templates. Until then do help with issue reporting or creating pull requests with any project templates missed there or feedback in general.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Generate nupkg using msbuild]]></title><description><![CDATA[Introduction Among many other advantages of using sdk style csproj in our solutions, to be able to generate nupkg without a nuspec file is a…]]></description><link>https://rubberduckdev.com/msbuild-nuget/</link><guid isPermaLink="false">https://rubberduckdev.com/msbuild-nuget/</guid><pubDate>Sat, 25 Jan 2020 11:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Among many other advantages of using sdk style csproj in our solutions, to be able to generate nupkg without a nuspec file is a major one. This is described in &lt;a href=&quot;https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package-msbuild&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft documentation - Create a NuGet package using MSBuild&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I agree that it is a big step towards being able to use msbuild to generate artifacts such as exe, dll and now nupkg. But the issues lies with the fact that the msbuild/csproj always assumes that it is a class library, thereby always adding a dll file to the nupkg.&lt;/p&gt;
&lt;h1&gt;Issue in detail&lt;/h1&gt;
&lt;p&gt;So the case we have is, we can generate a specific folder structure in a nupkg using nuspec file. And we hit issues when we try the same using a csproj file, in a bid to remove nuspec file usage.&lt;/p&gt;
&lt;h2&gt;The case - RubberDuckDev.AfterBuild.nupkg&lt;/h2&gt;
&lt;p&gt;The overall plan of this nupkg is to just keep a targets file in build folder.
So the folder structure is as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;RubberDuckDev.AfterBuild.nupkg
|____build
         |____
              RubberDuckDev.AfterBuild.targets&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So let us assume that we have nuspec file and we do not use msbuild. In such a case we will follow &lt;a href=&quot;https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package#run-nuget-pack-to-generate-the-nupkg-file&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Run nuget pack to generate the .nupkg file&lt;/a&gt; and use a nuspec file RubberDuckDev.AfterBuild.nuspec with the following contents.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&amp;lt;package xmlns=&quot;http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd&quot;&gt;
  &amp;lt;metadata&gt;
    &amp;lt;id&gt;RubberDuckDev.AfterBuild&amp;lt;/id&gt;
    &amp;lt;version&gt;1.0.0&amp;lt;/version&gt;
    &amp;lt;title&gt;RubberDuckDev targets file&amp;lt;/title&gt;
    &amp;lt;authors&gt;RubberDuckDev&amp;lt;/authors&gt;
    &amp;lt;owners&gt;RubberDuckDev&amp;lt;/owners&gt;
    &amp;lt;requireLicenseAcceptance&gt;false&amp;lt;/requireLicenseAcceptance&gt;
    &amp;lt;description&gt;Targets file from RubberDuckDev.&amp;lt;/description&gt;
    &amp;lt;summary&gt;Runs custom targets designed by RubberDuckDev.&amp;lt;/summary&gt;
  &amp;lt;/metadata&gt;
  &amp;lt;files&gt;
    &amp;lt;file src=&quot;build\**\*.*&quot; target=&quot;build&quot; /&gt;
  &amp;lt;/files&gt;
&amp;lt;/package&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To generate the nupkg, we just run:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;nuget pack RubberDuckDev.AfterBuild.nuspec&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now if we are to do this using a sdk style csproj is we create a csproj file RubberDuckDev.AfterBuild.csproj with nupkg related properties. Which is as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&amp;lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt;
  &amp;lt;PropertyGroup&gt;
    &amp;lt;GeneratePackageOnBuild&gt;true&amp;lt;/GeneratePackageOnBuild&gt;
    &amp;lt;PackageId&gt;RubberDuckDev.AfterBuild&amp;lt;/PackageId&gt;
    &amp;lt;Title&gt;RubberDuckDev targets file&amp;lt;/Title&gt;
    &amp;lt;Authors&gt;RubberDuckDev&amp;lt;/Authors&gt;
    &amp;lt;Owners&gt;RubberDuckDev&amp;lt;/Owners&gt;
    &amp;lt;Description&gt;Targets file from RubberDuckDev.&amp;lt;/Description&gt;
    &amp;lt;Summary&gt;Runs custom targets designed by RubberDuckDev.&amp;lt;/Summary&gt;
  &amp;lt;/PropertyGroup&gt;
  &amp;lt;ItemGroup&gt;
    &amp;lt;None Include=&quot;build\**\*.*&quot; Pack=&quot;True&quot; PackagePath=&quot;build\&quot; /&gt;
  &amp;lt;/ItemGroup&gt;
&amp;lt;/Project&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To generate the nupkg, we just run msbuild:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;msbuild RubberDuckDev.AfterBuild.csproj&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The problem is the nupkg generated will have an empty dll preset as csproj is assumed to be a class library by default. So the folder structure is now as follow, which what we did not want.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;RubberDuckDev.AfterBuild.nupkg
|____build
         |____
              RubberDuckDev.AfterBuild.targets
|____lib
         |____
              net46
                   |____
                        RubberDuckDev.AfterBuild.dll&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This create an unnecessary addition to our nupkg the RubberDuckDev.AfterBuild.dll file.&lt;/p&gt;
&lt;h1&gt;The solution - nuspec&lt;/h1&gt;
&lt;h2&gt;IncludeBuildOutput - Update on 18th Apr 2020&lt;/h2&gt;
&lt;p&gt;Recently I found out that nuspec is not necessarily the only solution. We can also &lt;a href=&quot;https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets#output-assemblies&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;disable inclusion of build output in nupkg&lt;/a&gt; by adding the following to sdk style csproj.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;IncludeBuildOutput&gt;false&amp;lt;/IncludeBuildOutput&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Solution using nuspec&lt;/h2&gt;
&lt;p&gt;The only way I could get it to work is by using a nuspec and csproj together. That is to use msbuild to generate the required folder structure in the nupkg.&lt;/p&gt;
&lt;p&gt;So the csproj finally looks like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&amp;lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt;
  &amp;lt;PropertyGroup&gt;
    &amp;lt;GeneratePackageOnBuild&gt;true&amp;lt;/GeneratePackageOnBuild&gt;
    &amp;lt;NuspecFile&gt;RubberDuckDev.AfterBuild.nuspec&amp;lt;/NuspecFile&gt;
  &amp;lt;/PropertyGroup&gt;
&amp;lt;/Project&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The RubberDuckDev.AfterBuild.nuspec file lives in the same folder as the csproj. Only msbuild-ing this csproj will give us the required nupkg with the correct folder structure and no empty dll file.&lt;/p&gt;
&lt;h2&gt;Versioning technique&lt;/h2&gt;
&lt;p&gt;I dislike editing files and patching versions at build time and, in my opinion, we should rather be passing in a property/variable to build process for versioning. We can achieve this using the csproj way of building the nupkg.&lt;/p&gt;
&lt;p&gt;For this, the csproj will look as follows.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&amp;lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt;
  &amp;lt;PropertyGroup&gt;
    &amp;lt;GeneratePackageOnBuild&gt;true&amp;lt;/GeneratePackageOnBuild&gt;
    &amp;lt;NuspecFile&gt;RubberDuckDev.AfterBuild.nuspec&amp;lt;/NuspecFile&gt;
    &amp;lt;Version Condition=&quot;&apos;$(Version)&apos; == &apos;&apos;&quot;&gt;1.0.0&amp;lt;/Version&gt;
    &amp;lt;NuspecProperties&gt;version=$(Version)&amp;lt;/NuspecProperties&gt;
  &amp;lt;/PropertyGroup&gt;
&amp;lt;/Project&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What it is saying is that, pass in a property into nuspec named as ‘Version’. And the default value of ‘Version’ is 1.0.0, unless ofcourse set to some other value.&lt;/p&gt;
&lt;p&gt;In order to accept and use the property, the nuspec has to change slightly.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&amp;lt;package xmlns=&quot;http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd&quot;&gt;
  &amp;lt;metadata&gt;
    &amp;lt;id&gt;RubberDuckDev.AfterBuild&amp;lt;/id&gt;
    &amp;lt;version&gt;$version$&amp;lt;/version&gt;
    &amp;lt;title&gt;RubberDuckDev targets file&amp;lt;/title&gt;
    &amp;lt;authors&gt;RubberDuckDev&amp;lt;/authors&gt;
    &amp;lt;owners&gt;RubberDuckDev&amp;lt;/owners&gt;
    &amp;lt;requireLicenseAcceptance&gt;false&amp;lt;/requireLicenseAcceptance&gt;
    &amp;lt;description&gt;Targets file from RubberDuckDev.&amp;lt;/description&gt;
    &amp;lt;summary&gt;Runs custom targets designed by RubberDuckDev.&amp;lt;/summary&gt;
  &amp;lt;/metadata&gt;
  &amp;lt;files&gt;
    &amp;lt;file src=&quot;build\**\*.*&quot; target=&quot;build&quot; /&gt;
  &amp;lt;/files&gt;
&amp;lt;/package&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice how the version element now uses a property $version$.&lt;/p&gt;
&lt;p&gt;With this we can generate RubberDuckDev.AfterBuild.2.0.0.nupkg using the command:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;msbuild RubberDuckDev.AfterBuild.csproj /P:Version=2.0.0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And this will also give us the expected folder structure. And no empty dll file.&lt;/p&gt;
&lt;h1&gt;Advantages of using msbuild&lt;/h1&gt;
&lt;p&gt;We could achieve the folder structure in the nupkg using the nuget pack technique we discussed earlier. But there are more advantages to using msbuild in my opinion.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One tool to build and generate artifacts. No need to use msbuild for building and nuget for generating nupkg. We can also do a nuget restore using msbuild using command &lt;code class=&quot;language-text&quot;&gt;msbuild RubberDuckDev.AfterBuild.csproj /t:Restore&lt;/code&gt;. So msbuild can serve multiple purposes.&lt;/li&gt;
&lt;li&gt;Easier versioning technique. No need to edit nuspec file during build to generate new versions.&lt;/li&gt;
&lt;li&gt;More aligned development, build &amp;#x26; test technique using only one tool.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;It makes development, build, &amp;#x26; test easier if we can have multiple csproj in one visual studio solution and not have to add special cases for nuget pack in our scripts. Although sdk style csproj helps us generate nupkg, it is yet to replace nuspec files. I hope it does.&lt;/p&gt;
&lt;p&gt;Is there a better technique to build nupkgs? Do let me know.
If you agree/disagree with this post, please comment or tweet about it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Azure DevOps flaky test identification & reporting]]></title><description><![CDATA[Introduction We will agree that flaky tests make software development a challenging affair. Such tests add non-determinism and uncertainty…]]></description><link>https://rubberduckdev.com/flaky-test-detection/</link><guid isPermaLink="false">https://rubberduckdev.com/flaky-test-detection/</guid><pubDate>Sun, 08 Dec 2019 11:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;We will agree that flaky tests make software development a challenging affair. Such tests add non-determinism and uncertainty to our test suite.&lt;/p&gt;
&lt;p&gt;“Non-deterministic tests have two problems, firstly they are useless, secondly they are a virulent infection that can completely ruin your entire test suite.”
— Martin Fowler &lt;a href=&quot;https://martinfowler.com/articles/nonDeterminism.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Eradicating Non-Determinism in Tests&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Although we find it frustrating, it is inevitable that as a codebase grows and becomes more complicated, we get some flaky tests. Be it because of simple reasons as bad setup/teardown or dependency on third party components to difficult to avoid situations such as infrastructure or concurrency.&lt;/p&gt;
&lt;h2&gt;Shoutout to Azure advent calendar&lt;/h2&gt;
&lt;p&gt;This blog post is published as part of &lt;a href=&quot;https://azureadventcalendar.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure advent calendar&lt;/a&gt;. I would highly recommend a visit to the advent calendar and check out the other amazing topics published there. All videos on the calendar are at &lt;a href=&quot;https://www.youtube.com/channel/UCJL9wCcmeMBbah4J0uOWIPg&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Advent Calendar Youtube channel&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Flaky test detection on Azure DevOps&lt;/h1&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 500px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9025b81fd1cb7fa2b765bea0c274cfd7/d1f95/flaky-test.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.08532423208192%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMCBAX/xAAWAQEBAQAAAAAAAAAAAAAAAAADAAH/2gAMAwEAAhADEAAAAUvTctziQhf/xAAZEAACAwEAAAAAAAAAAAAAAAAAAQIDESH/2gAIAQEAAQUCg2yzYj6RRb0w/8QAFxEAAwEAAAAAAAAAAAAAAAAAARAREv/aAAgBAwEBPwETK//EABcRAAMBAAAAAAAAAAAAAAAAAAEQERL/2gAIAQIBAT8BN0v/xAAbEAABBAMAAAAAAAAAAAAAAAARAAEQIQJRYf/aAAgBAQAGPwIa4qglNjUf/8QAGRABAAMBAQAAAAAAAAAAAAAAAQARITFR/9oACAEBAAE/IUAIGoIijvhBa3sXgExCl7FCk//aAAwDAQACAAMAAAAQQ/8A/8QAFhEAAwAAAAAAAAAAAAAAAAAAECFR/9oACAEDAQE/EIWP/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAEhMf/aAAgBAgEBPxBE1CH/xAAdEAEBAAIBBQAAAAAAAAAAAAABEQAxYSFBUZHB/9oACAEBAAE/ELzy0NxBhepUPQcZTercAMYC8ZEfF7GHRFC34c5uZGUz/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Flaky test - Fry&quot;
        title=&quot;Flaky test - Fry&quot;
        src=&quot;/static/9025b81fd1cb7fa2b765bea0c274cfd7/d1f95/flaky-test.jpg&quot;
        srcset=&quot;/static/9025b81fd1cb7fa2b765bea0c274cfd7/3c01f/flaky-test.jpg 293w,
/static/9025b81fd1cb7fa2b765bea0c274cfd7/d1f95/flaky-test.jpg 500w&quot;
        sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If you have got the same issue as &lt;a href=&quot;https://en.wikipedia.org/wiki/Philip_J._Fry&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Fry&lt;/a&gt; then here’s a video explanation on how to use Azure DevOps to assist in identifying and reporting flaky tests.&lt;/p&gt;
&lt;p&gt;
          &lt;div
            class=&quot;gatsby-resp-iframe-wrapper&quot;
            style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden;margin-bottom: 1rem&quot;
          &gt;
            &lt;div class=&quot;embedVideo-container&quot;&gt;
            &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/m2KSh0JrM-Q?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0;
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
          &quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
        &lt;/div&gt;
          &lt;/div&gt;
          &lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;If you have any thoughts or comments please do get in touch with me on twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;. Or use the disqus plugin below.&lt;/p&gt;
&lt;h3&gt;Credits&lt;/h3&gt;
&lt;p&gt;Flaky test banner by &lt;a href=&quot;https://baileymcginn.com/about&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bailey McGinn&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[AZ900 - Simple Tips for Microsoft Certification Exam]]></title><description><![CDATA[Introduction As in any other professional field, we know the value of self-improvement. It not only helps us face the competition, but we…]]></description><link>https://rubberduckdev.com/az900-exam/</link><guid isPermaLink="false">https://rubberduckdev.com/az900-exam/</guid><pubDate>Sat, 19 Oct 2019 11:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;As in any other professional field, we know the value of self-improvement. It not only helps us face the competition, but we also get to add value to our field of expertise. This is what inspired me to take the Microsoft certification exam. My journey is far from over as I still have at least two other exams to take. Anyway, this is my first post about my experience of the process and any tips I could share. Please note that as per the NDA we sign before taking the exam, I might not be able to give away a lot. This might be a good thing, so that, you get to experience a lot of it on your own.&lt;/p&gt;
&lt;h2&gt;Why get a Microsoft Certification?&lt;/h2&gt;
&lt;p&gt;Goes without saying, Microsoft is already an established brand name in the field of Software Engineering. So being vouched by them for our skills is quite well received by peers, employers, recruiters and more. If my opinion doesn’t sound convincing, Microsoft themselves provide a detailed page on &lt;a href=&quot;https://www.microsoft.com/en-us/learning/certification-benefits.aspx&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;why get certified&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Finding the right certification&lt;/h2&gt;
&lt;p&gt;Microsoft has revamped its certification website to be &lt;a href=&quot;https://www.microsoft.com/en-us/learning/default.aspx&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft Learning&lt;/a&gt;. It has lots of information including the one we saw earlier ‘Why get certified?’ and more.
The simplest way to find what certification suits you is to &lt;a href=&quot;https://www.microsoft.com/en-us/learning/browse-all-certifications.aspx&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;browse all certifications&lt;/a&gt;. There you can filter based on your needs.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1017px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6061b0e51e6b99b92c67a01c931e7a13/2cd87/all-certifications-filtered.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.84641638225256%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACyklEQVQoz0WP3UtTcRjHf4ZEN92EWqCoTO0i1HSRdFdomZIkJZFF4Sv04qbpRWSoF+LLdBZKoqhlWs5tuT8hjSQJzDIWvWhzL+d1O2fb2dk8287vnLNfbEp++d58vzwfnucBqbWG1FpDys2FozVzRZqlknbLaY1R3bZYpH1boJkv1L4ueDij1s6UaKfOtkyo742mVHcfq3pyvPpp2tVOkCANGXdN2fXGIu3SuQ5LcaupuHWxUPOmsHW+uG2uoGVGrZlWt0yWaCbO3B/LuzOoqu3NqOlKv9YFTtxeTLtlyG16l/9gKadpMbd5QdU0r2qey2p8mdk4ldk4md0wntPwQlU3mlf37GSd/lTdUH69LutGT/r1bpBcNZtcNZt05RWonAaVk6BiHFSMgfLn4LIelOvApT5wsReU9YCyLlDaCUofJ13oOHS+7UhZ++HSR2DYYh22WPUW69DS90Hz1wHzxqD5y4B5vd/8uc+81mda6zd96jeu6kyrQ6aPOuPKsHFlxLQ8YlrWG98DdCAFoSiKiQlDhMREhCgmIwQTluOOx32BSBRyfJBhWA/D+vwcHwx5vT6W9fHBEB8McQHew7Cs18cFeI7nPQxD0TTDsn4uIAhhIEkShFBRFFmWIYR7UZIgQmivgRCKohiLKZIseRgPQRKsl+WDfDQaBRDG537+sbkICiEUi8UQQkI4sr75A0Lpf+PESOuvLYLAtra3aZqmaCocDu/D37bxv4Q3/ndiNByJfti0SZL8H97CmI3fLtbjJinK7XbjOBGJROJnS7LEcX6CwJ0ul8Ph4PmgKEZpiiIIwuV00jQlKwrH+WmKpGgaw7DQbgghJElSHJZlmaYoHMf9Pj/LspGE7PYdu8OBYdiuICCEGIbZsdsdDofNZgsEArIsH8A+r48gSbfbTZGUIAiiGCVJkkws9/s5hFCAC5AJ4RguCIKiKBDCf0b8P7APaXI/AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Role based certifications&quot;
        title=&quot;Role based certifications&quot;
        src=&quot;/static/6061b0e51e6b99b92c67a01c931e7a13/2cd87/all-certifications-filtered.png&quot;
        srcset=&quot;/static/6061b0e51e6b99b92c67a01c931e7a13/8890b/all-certifications-filtered.png 293w,
/static/6061b0e51e6b99b92c67a01c931e7a13/1f316/all-certifications-filtered.png 585w,
/static/6061b0e51e6b99b92c67a01c931e7a13/2cd87/all-certifications-filtered.png 1017w&quot;
        sizes=&quot;(max-width: 1017px) 100vw, 1017px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Now that you know how to get to the list of available certifications, it is now up to you as to what to choose. Depending on what you are passionate about, or what your current/future role demands are, filter out and select your certification for further details.&lt;/p&gt;
&lt;h1&gt;Microsoft Certified Azure Fundamentals&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.microsoft.com/en-us/learning/azure-fundamentals.aspx#cert-expansion-tab-exam-az-900&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;AZ-900 Microsoft Azure Fundamentals&lt;/a&gt; exam is an important milestone to get into a Microsoft certification path with Microsoft Azure. This exam measures your ability to understand the following concepts: cloud concepts; core Azure services; security, privacy, compliance, and trust; and Azure pricing and support.&lt;/p&gt;
&lt;p&gt;As it stands, the AZ-900 exam is optional if you aim at a certification such as &lt;a href=&quot;https://www.microsoft.com/en-us/learning/azure-administrator.aspx&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft Certified: Azure Administrator Associate&lt;/a&gt; or &lt;a href=&quot;https://www.microsoft.com/en-us/learning/azure-developer.aspx&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft Certified: Azure Developer Associate&lt;/a&gt;. Nevertheless, it is quite the foundation stone upon which you can base your further learning. And if you have not taken any exams with Microsoft, starting with this one will give you more insight into the exam process itself if not just the technical knowledge around Azure fundamentals.&lt;/p&gt;
&lt;h1&gt;Preparation&lt;/h1&gt;
&lt;p&gt;The following are the tips on how to prepare for the AZ-900 exam. Based on what I did. As I progress towards more difficult areas of exams, I might adapt these and will do future blog posts about them.&lt;/p&gt;
&lt;h2&gt;Studying&lt;/h2&gt;
&lt;p&gt;The initial ‘must-do’, in my opinion, is to start with the self-paced interactive learning path as it would be recommended on the exam page. In the case of AZ-900, it is the &lt;a href=&quot;https://docs.microsoft.com/en-us/learn/paths/azure-fundamentals/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Fundamentals Learning Path&lt;/a&gt;. This is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Gamification&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;gamified&lt;/a&gt; learning path, which got me interested for a few days. And I learnt quite a bit, even though I started with a bias that it all might be easy. The fundamentals have some really exciting points which are a must know if you are aiming to be an expert in Azure. Take the concept of Azure Government for instance or why Azure Germany is unique non-government cloud or availability sets vs availability zones. I had no idea about these. In case you do not too, do follow the learning path.&lt;/p&gt;
&lt;p&gt;I did search for related topics on &lt;a href=&quot;https://pluralsight.com&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Pluralsight&lt;/a&gt; but given this is a fundamentals exam, I didn’t find any directly related learning paths on Pluralsight. Will continue checking for future exams and will add to posts then.&lt;/p&gt;
&lt;h2&gt;Practice Exams&lt;/h2&gt;
&lt;p&gt;I used the practise exams recommended on the exam page.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 608px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9d91693c57044369829ef2476f05a98a/f7382/practice-exams.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.06143344709898%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACRUlEQVQoz41TS24UMRDt4yAkFpFQOAEL7sQCFDJruAR3IChBSQSrfEkkMtNBmWl3t7/9c9vu9rfBMwksQIgn+emV5SqXq8pJmqbz+WKRpt/T9Pzi0lg7/TcS75yz1nvvnHXRcP4Bzt/rzWb4A4nROs/zIs+Xq9Xd8i4DGQA5yLJVBvKiWC6X0QDZMOq/30wowRizNSillKyJEIJwZIwHpbQerYkYR621icuYxHtX1RHxKGVN3VRrJoSwqqqbCiNCKYOsxRXvhRRCKKWEFFLKxFqHMcGYFEUJIYIIY0wRIgRTjDGEkMSYrKpbwqpR6xCCcy6EENO21iKECYZ5DsoiR6ikjCAUo0BY5gAwSquq4l3bNE3XtT3v27ZVQkXnybBw/oLvb3WftuXhNj/Y5gfP/OfHE3kfaxLcP1tl+LB421y+7q53u6ud9uub9momrl4qdDSYoJRUsVqxRGY9A7FzdT1cX7vNm1nnceNaGWgbWBcYD7j2JeGEUgjLsiiarhNa87ppbr7Ro2N+cWkYC94nk6H+5Hmz96T+uNXtb9V7kfnBU3/8aOq+rLPz0zDYNB3OzsbbWz8Mv9OebB+W7/TNjp7PzHxXz3fNYmYWM5++Ct3c0laenfKTEwWANSYWIQT/MHCJ96F3kwrTEO55DJPyoZum4cOeODocOffe/3KLHDbSJda5VYHvAFyVZFUigAhANIMEQNxQoqyR49hzLkQvpRR9H4WQfd8755MYxFpr9OaHOGu8i7Z3VhtzP4rrYdQPYqN/pv0DpHhAxnSePrEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Practice Exams&quot;
        title=&quot;Practice Exams&quot;
        src=&quot;/static/9d91693c57044369829ef2476f05a98a/f7382/practice-exams.png&quot;
        srcset=&quot;/static/9d91693c57044369829ef2476f05a98a/8890b/practice-exams.png 293w,
/static/9d91693c57044369829ef2476f05a98a/1f316/practice-exams.png 585w,
/static/9d91693c57044369829ef2476f05a98a/f7382/practice-exams.png 608w&quot;
        sizes=&quot;(max-width: 608px) 100vw, 608px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I took lots of mock exams. The service provider &lt;a href=&quot;https://pts.measureup.com&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;measureup&lt;/a&gt; provides a full history of the practice exams. The pass mark in practice exams is 80% and initially, I scored 67%. But within a few days of those exams, I started scoring around 80%. I aimed to score 100% consistently and then book the real exam. But I did lose patience and when I consistently started scoring 90%+ I booked the exam.&lt;/p&gt;
&lt;h2&gt;Extras&lt;/h2&gt;
&lt;h3&gt;Listening&lt;/h3&gt;
&lt;p&gt;The overall idea of the certification exercise is continuous learning and getting vouched for it. So I think that should be the case in other parts of your professional activities as well. For example, if you have got a colleague who knows more about Azure than you, it is a good idea to get help, discuss ideas and listen to them. Listening is very important, followed by filtering and keeping what you believe is important for you.&lt;/p&gt;
&lt;h3&gt;Local Events&lt;/h3&gt;
&lt;p&gt;I am lucky to be working at Oxford which means I get to attend events such as &lt;a href=&quot;https://www.dotnetoxford.com&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;.Net Oxford&lt;/a&gt;, &lt;a href=&quot;https://www.meetup.com/Azure-Oxford/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Azure Oxford&lt;/a&gt; or &lt;a href=&quot;https://www.meetup.com/devopsoxford/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;DevOps Oxford&lt;/a&gt;. These may not always be doing something directly related to AZ-900 or even Azure. But one of the techniques we all use consciously or subconsciously is taking an educated guess while answering. This is where stimulating our brain comes in which in turn helps when we end up guessing applying certain method, process or logic which we can pick up from like-minded people.&lt;/p&gt;
&lt;h1&gt;Taking the exam&lt;/h1&gt;
&lt;p&gt;Having done all the preparation, taking the exam was less stressful than I’d have thought. As far as I know, there are two ways to take the exam.&lt;/p&gt;
&lt;h2&gt;Exam Center&lt;/h2&gt;
&lt;p&gt;When you schedule the exam, it leads you through a form to select an exam centre and timing as per the availability. Then on the scheduled time or normally 15mins before your exam, you reach the venue, go through an initial test exam and then take your exam. While scheduling exam I had chosen Pearson VUE, which is what most of us will choose. The other option is Certiport and you should choose that if you are attending a school or are an instructor.&lt;/p&gt;
&lt;p&gt;I have also taken &lt;a href=&quot;https://www.youracclaim.com/badges/e73e350b-d77e-402a-9575-6e37d4921e8f/public_url&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Exam 483: Programming in C#&lt;/a&gt; in the past at the exam centre in Reading, Berkshire, UK. So this time around, I thought it would be good to try out the second option, BYOD.&lt;/p&gt;
&lt;h2&gt;Proctored Exam: BYOD&lt;/h2&gt;
&lt;p&gt;BYOD (bring your own device) proctored exam is new to me and hence I chose this option for my AZ-900 exam.&lt;/p&gt;
&lt;p&gt;The way it works is you take the exam on your chosen laptop and someone (or more people) will proctor the exam from a remote location. This means your machine will have to run a test provided by them before the exam starts and camera, microphone etc need to be working and switched on and your location is away from any kind of disruptions.&lt;/p&gt;
&lt;p&gt;This is ok if you are taking the exam from an office meeting room where you are certain that nobody will knock or come in. I took the exam from my home office room, which was fine disruption wise, but it felt like letting a stranger(s) into my room. Next exam, I might take it from an office room or exam centre.&lt;/p&gt;
&lt;p&gt;Having said so, I can see the benefit of BYOD for people who may not be able to go to the exam centre. It needs further improvements, but I do congratulate MeasureUp on implementing it.&lt;/p&gt;
&lt;h1&gt;Results&lt;/h1&gt;
&lt;p&gt;The exam results are generated immediately after the exam and it includes marks scored with a further breakdown of performance.&lt;/p&gt;
&lt;p&gt;Having taken the exam, you can get details on your history, generate badges, certificates to share etc at &lt;a href=&quot;https://www.microsoft.com/en-us/learning/dashboard.aspx&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Microsoft Learning Dashboard&lt;/a&gt;. Although this is present, I have struggled with the UI in the past and the Pearson VUE (the exam service provider) web site to get exam result details. Hence I always keep a pdf of the exam the moment it gets generated.&lt;/p&gt;
&lt;p&gt;The badges are provided by &lt;a href=&quot;https://www.youracclaim.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Acclaim&lt;/a&gt; for Microsoft. You can view and share the badges from acclaim’s website.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I managed to learn a lot more than Azure Fundamentals while taking the AZ-900 exam. Hoping this post helps others in preparation of a Microsoft exam. If you have any thoughts or comments please do get in touch with me on twitter &lt;a href=&quot;https://twitter.com/rubberduckdev&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@rubberduckdev&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[DDD14 Reading 2019]]></title><description><![CDATA[Developer! Developer! Developer! Day The DDD14 held at Reading on 12th Oct 2019 was my first ever experience of Developer Developer…]]></description><link>https://rubberduckdev.com/ddd14/</link><guid isPermaLink="false">https://rubberduckdev.com/ddd14/</guid><pubDate>Sun, 13 Oct 2019 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Developer! Developer! Developer! Day&lt;/h1&gt;
&lt;p&gt;The DDD14 held at Reading on 12th Oct 2019 was my first ever experience of &lt;a href=&quot;https://developerdeveloperdeveloper.com/Home/About&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Developer Developer Developer&lt;/a&gt; community event. It is a fantastic event and I have been missing out by not attending it in the past. The quality of speakers and the content of presentations were amazing, add to it the great organization and location (it was at Microsoft UK HQ at Thames Valley Park). If you are a member of the software community, definitely do not miss out on this great event.&lt;/p&gt;
&lt;p&gt;Here are a few of the talks I could attend on the day.&lt;/p&gt;
&lt;h1&gt;How to steal an election&lt;/h1&gt;
&lt;p&gt;By &lt;a href=&quot;http://www.twitter.com/@garyshort&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Gary Short&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This was a great talk to start with. It comes with a controversial title and goes on to excite and amaze the audience with an explanation of multiple techniques that can be applied and to a large extent is applied to steal an election. By ‘steal an election’, as was explained by Gary, it means “to illegally/immorally influence a result contrary to the current mood of the electorate”.&lt;/p&gt;
&lt;p&gt;We may already be aware of many of the techniques used by political parties to try to influence their voters such as
Caging: where hostile voters are stripped of their right to vote or discouraged enough to not to vote.
Gerrymandering: re-architect district borders to gain a numbers advantage.
Tossing: creating situations when votes cast can be discarded as invalid.&lt;/p&gt;
&lt;p&gt;Although the extra exciting bit provided was the real world examples used by Gary to show how some parties are currently doing it. He goes to use the audience to participate in something which we would normally wouldn’t agree to do, to prove how influence works.&lt;/p&gt;
&lt;p&gt;Overall, both scary and exciting and the full video is available on Youtube.

          &lt;div
            class=&quot;gatsby-resp-iframe-wrapper&quot;
            style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden;margin-bottom: 1rem&quot;
          &gt;
            &lt;div class=&quot;embedVideo-container&quot;&gt;
            &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/32m8luvA9Qg?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0;
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
          &quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
        &lt;/div&gt;
          &lt;/div&gt;
          &lt;/p&gt;
&lt;h1&gt;Air Quality, Azure Functions and Spider Eggs&lt;/h1&gt;
&lt;p&gt;By &lt;a href=&quot;http://www.twitter.com/@robmiles&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Rob Miles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This talk is actually about IoT devices, use of Microsoft Azure technologies as well as measuring air quality, well just to start with, but was a whole lot more. It was clear how passionate Rob is towards his projects and technology in general and it showed in his presentation when he insisted on trying hobby projects is the best way to learn.&lt;/p&gt;
&lt;p&gt;From the air quality measurement side of things, Rob did come with some amazing gadgets and a classy top hat (with air quality sensors on it). To begin with, he demonstrated connecting a device using &lt;a href=&quot;https://en.wikipedia.org/wiki/MQTT&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Message Queue Telemetry Transport&lt;/a&gt; with a simplified architecture as below.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 843px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/52a3587bb611039686860d6803beb86c/51384/mqtt-air-quality.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.986348122866893%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABQ0lEQVQY03WPu0vDcBSF88fVTQdREERx6OTiKAqKIDoooiA4uSmIIKIgKtJqW1OxiiKttrZDH0kfSQoJTbEPmv7ySTJV1A8O9w6Xc+6RVFXFUzqdQVEUyuUymqb5u6ee4yCES18I+sLFdV08BuegJF3XsUwTVVFoNGzsZtM3rFSq/vwLIX6aDCL5KfzPS87gIa2RyOjI7zVMu/PrxvUChPDeRUrVTGavXtlN5KjWmxQ0G8VoklUt7K8uUxsRRlZCjK/dEVi85uKxxFfHod5oo5stPqsWS5EUy9EPFsJJJLmoEziWmb9LEn0rcyoXuH5WOAhlMawWwR2ZyY17ZrZkRldv2b9Mcx7PcxbPc3iT5fQpz/BJnLGzBENHMSSz1UUuGaQ061cVpy+Y24szvRkhuH3PxHrYrz5Iu+cQK+jEigaxos43o3uvLstGY9AAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;MQTT Air Quality Device Architecture&quot;
        title=&quot;MQTT Air Quality Device Architecture&quot;
        src=&quot;/static/52a3587bb611039686860d6803beb86c/51384/mqtt-air-quality.png&quot;
        srcset=&quot;/static/52a3587bb611039686860d6803beb86c/8890b/mqtt-air-quality.png 293w,
/static/52a3587bb611039686860d6803beb86c/1f316/mqtt-air-quality.png 585w,
/static/52a3587bb611039686860d6803beb86c/51384/mqtt-air-quality.png 843w&quot;
        sizes=&quot;(max-width: 843px) 100vw, 843px&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Following this, the talk was more about using Azure IoT Hub and Azure Functions for managing MQTT messages and interacting with the devices.&lt;/p&gt;
&lt;p&gt;Among many devices, Rob described using LoRaWAN to design cow tracking devices and leaving it our imagination to use it for other creative solutions. All this and more are available at &lt;a href=&quot;https://github.com/CrazyRobMiles/DDD-Reading-2019&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Rob’s github&lt;/a&gt; account including all source code.&lt;/p&gt;
&lt;h1&gt;Internationalising your applications&lt;/h1&gt;
&lt;p&gt;By &lt;a href=&quot;http://www.twitter.com/@petevick&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Pete Vickers&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Microsoft’s &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/uwp/design/globalizing/use-mat&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Multilingual App Toolkit&lt;/a&gt; was the main focal point of this talk. Pete presented how the process of translation is now comparatively easier with the use of the toolkit. The demonstration was on Pete’s &lt;a href=&quot;http://www.gui-innovations.com/restaurant-app.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Restaurant App&lt;/a&gt;. The demonstration included complicated cases of languages which are written from right to left (such as Arabic or Hebrew, although only Arabic was included in the demo), languages with the double-byte character set such as Japanese and also languages that use umlaut or similar notations such as German.&lt;/p&gt;
&lt;h1&gt;Typescript for the C# developer&lt;/h1&gt;
&lt;p&gt;By &lt;a href=&quot;http://www.twitter.com/@shawty_ds&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Peter Shaw&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Peter presented a great take on Typescript from a C# developer’s perspective. While playing to the dislike we share towards JavaScript due to its anomalistic behaviour, Peter advocated the adoption of Typescript which I wholeheartedly agree with. The funniest bit of the presentation was the clip from Gary Bernhardt’s talk titled Wat, again showing how complicated JavaScript can be. If you have not already seen the video, do take a look at &lt;a href=&quot;https://www.destroyallsoftware.com/talks/wat&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Wat&lt;/a&gt;, I promise you that it is hilarious.&lt;/p&gt;
&lt;p&gt;While promising not to dive in-depth into the world of Typescript, Peter started with clearing up many misconceptions we might have about Typescript. Followed by the explaining how the goodness of C# which we miss in JavaScript is present in Typescript. Having said so, Typescript has its complications as well, the ‘null vs undefined madness’ for example.&lt;/p&gt;
&lt;p&gt;Do check out the &lt;a href=&quot;http://files.digital-solutions.me.uk/presentations/typescript.pdf&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;slides&lt;/a&gt; of the fantastic talk.&lt;/p&gt;
&lt;h1&gt;.Net Configuration is Easy … Right?&lt;/h1&gt;
&lt;p&gt;By &lt;a href=&quot;http://www.twitter.com/@stevetalkscode&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Steve Collins&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Steve starts with a brief overview of the history of configuration in .NET Framework and how things have changed for dotnet core, some for the good while some issues remain.&lt;/p&gt;
&lt;p&gt;The best bit of the talk is that it suggests a “SOLID” based approach that makes configuration not only fully testable, but adds enhancements to handle encrypted configuration values. To achieve this, Steve used multiple design patterns and mainly focused on &lt;a href=&quot;http://www.blackwasp.co.uk/gofpatterns.aspx&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bridge pattern originally suggested by Gang of Four&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The code for this demo and a link to Steve’s blog post are available at &lt;a href=&quot;https://github.com/configureappio/ConfiguarationBridgeCrypto&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;his github&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Overall, the experience at my first ever DDD event was fantastic. I would highly recommend it as it teaches and inspires you, like no other event I know of.&lt;/p&gt;</content:encoded></item></channel></rss>