{"id":324,"date":"2019-09-22T18:25:53","date_gmt":"2019-09-23T01:25:53","guid":{"rendered":"http:\/\/blog.nillsf.com\/?p=324"},"modified":"2019-09-22T18:26:03","modified_gmt":"2019-09-23T01:26:03","slug":"automating-clean-up-of-demo-resources","status":"publish","type":"post","link":"https:\/\/blog.nillsf.com\/index.php\/2019\/09\/22\/automating-clean-up-of-demo-resources\/","title":{"rendered":"Automating clean-up of demo resources"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">A while ago I wrote about a <a href=\"https:\/\/blog.nillsf.com\/index.php\/2019\/09\/04\/creating-a-self-destructing-vm\/\">self-destructing VM<\/a>. The process was completely self-contained, and the VM deletes itself. This was a nice demo &#8211; but not too useful in my day-to-day, where I create demo&#8217;s of all kinds of different resources.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In my case it&#8217;d be a lot better to have a daily automation script that would clean up after me. Kind of like a Roomba for Azure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s think this through, think about what is required for this, and then build this out.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Architecture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">From a timeline perspective, this would have to look something like this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"306\" src=\"\/wp-content\/uploads\/2019\/09\/image-1-1024x306.png\" alt=\"\" class=\"wp-image-325\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-1-1024x306.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-1-300x90.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-1-768x230.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-1.png 1795w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at this from right to left, this means we would need a system that asks for confirmation prior to deleting, that either deletes the resources or changes the tag value to a future date. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This confirmation is triggered from a daily script that reads the tags, and triggers the confirmation email if the &#8220;to delete by&#8221; date is in the past. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, we need a (preferably) automated way to tag our resources with a date that we want them to be deleted by.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Reasonably speaking, I see two options of <em>&#8220;things&#8221;<\/em> we can delete, either a full resource group or actual individual resources. As I hate too many confirmations, I will create my workflow based on the resource groups rather than the resources themselves.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So, thinking about options for the implementation, these are the options I see:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Tagging: Manual tagging &#8211; Azure Policy auto-tagging<\/li><li>Daily script: Azure Automation &#8211; Logic Apps &#8211; Functions<\/li><li>Confirmation: Logic Apps<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">My choice for the end-to-end falls on policy auto-tagging + Azure automation + Logic Apps. Why? I picked auto-tagging because I&#8217;m lazy and I&#8217;ll probably forget tagging my new resource groups. I picked Azure automation for checking the tags, as this is an easy environment to schedule recurring tasks, and it supports code-based development, which will give me easy ways to parse dates. I picked Logic Apps for the confirmation, because that mechanism is built in. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The only thing that worries me right now, is auto-tagging resources with a &#8220;to delete by&#8221;-date. I&#8217;m thinking of putting logic in place that tags resources to be deleted &#8220;next week&#8221;. The automation script and the logic app have me less worried. So, why don&#8217;t we get started with the hardest task: tagging resources with a future date. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Auto-tagging resources<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Auto-tagging itself is quiet straightforward. There is a default Azure Policy, that will append a tag and set it&#8217;s default value. However, this default value by default is a hard-coded string:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"846\" height=\"1024\" src=\"\/wp-content\/uploads\/2019\/09\/image-2-846x1024.png\" alt=\"\" class=\"wp-image-326\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-2-846x1024.png 846w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-2-248x300.png 248w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-2-768x930.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-2.png 967w\" sizes=\"auto, (max-width: 846px) 100vw, 846px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I stumbled upon <a href=\"https:\/\/jrudlin.github.io\/2019-07-18-azure-policy-createdon-date\/\">this blog<\/a>, which adds a CreatedOnDay tag. That&#8217;s nice, because that brings us halfway there! IF (big IF) Azure Policy allows arithmetic functions on dates, we&#8217;ll be all set. Let&#8217;s try this out.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First things first. have a look at the documentation of <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/azure-resource-manager\/resource-group-template-functions-string#utcnow\">this function<\/a>. We have the ability to format this more nicely, which I&#8217;ll do. Next, the ARM language has a function <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/azure-resource-manager\/resource-group-template-functions-numeric#add\">to do additions<\/a>, but it requires two integers as inputs. I highly doubt this will work, but let&#8217;s give it a swing. My policy now looks something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"mode\": \"All\",\n  \"policyRule\": {\n    \"if\": {\n      \"allOf\": [\n        {\n          \"field\": \"tags['CreatedOnDate']\",\n          \"exists\": \"false\"\n        },\n        {\n          \"field\": \"type\",\n          \"equals\": \"Microsoft.Resources\/subscriptions\/resourceGroups\"\n        }\n      ]\n    },\n    \"then\": {\n      \"effect\": \"append\",\n      \"details\": [\n        {\n          \"field\": \"tags['CreatedOnDate']\",\n          \"value\": \"[utcNow('d')]\"\n        },\n                {\n          \"field\": \"tags['DeleteByDate']\",\n          \"value\": \"[add(utcNow('d'),7)]\"\n        }\n      ]\n    }\n  },\n  \"parameters\": {}\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"> Let&#8217;s give this a spin. And giving it a spin, gave me an immediate error:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"529\" height=\"323\" src=\"\/wp-content\/uploads\/2019\/09\/image-3.png\" alt=\"\" class=\"wp-image-327\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-3.png 529w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-3-300x183.png 300w\" sizes=\"auto, (max-width: 529px) 100vw, 529px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Which means, the pretty print of the <code>utcNow()<\/code> apparently doesn&#8217;t work in a policy definition. I can live with that. Updating my policy to just have <code>utcNow()<\/code> &#8211; I get the error I was expecting, that the add doesn&#8217;t support adding to our date, which is a string:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"147\" src=\"\/wp-content\/uploads\/2019\/09\/image-4-1024x147.png\" alt=\"\" class=\"wp-image-328\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-4-1024x147.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-4-300x43.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-4-768x110.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-4.png 1382w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">So we&#8217;ll remove the DeleteByDate for now, but keep the CreatedOnDate in place. This works. Just for reference, this is the policy I put in place:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"mode\": \"All\",\n  \"policyRule\": {\n    \"if\": {\n      \"allOf\": [\n        {\n          \"field\": \"tags['CreatedOnDate']\",\n          \"exists\": \"false\"\n        },\n        {\n          \"field\": \"type\",\n          \"equals\": \"Microsoft.Resources\/subscriptions\/resourceGroups\"\n        }\n      ]\n    },\n    \"then\": {\n      \"effect\": \"append\",\n      \"details\": [\n        {\n          \"field\": \"tags['CreatedOnDate']\",\n          \"value\": \"[utcNow('d')]\"\n        }\n      ]\n    }\n  },\n  \"parameters\": {}\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If we now create a new resource group &#8211; we should see it tagged with the current date.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"760\" height=\"382\" src=\"\/wp-content\/uploads\/2019\/09\/image-5.png\" alt=\"\" class=\"wp-image-329\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-5.png 760w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-5-300x151.png 300w\" sizes=\"auto, (max-width: 760px) 100vw, 760px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Going into tags for my resource group, I don&#8217;t see any tags. I clearly forgot something. <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"239\" src=\"\/wp-content\/uploads\/2019\/09\/image-6-1024x239.png\" alt=\"\" class=\"wp-image-330\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-6-1024x239.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-6-300x70.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-6-768x179.png 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">What I did forget was to create a policy assignment as well. We only created the definition, not the assignment. Let&#8217;s do this and create a new resource group.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"849\" height=\"373\" src=\"\/wp-content\/uploads\/2019\/09\/image-7.png\" alt=\"\" class=\"wp-image-331\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-7.png 849w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-7-300x132.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-7-768x337.png 768w\" sizes=\"auto, (max-width: 849px) 100vw, 849px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"683\" height=\"388\" src=\"\/wp-content\/uploads\/2019\/09\/image-8.png\" alt=\"\" class=\"wp-image-332\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-8.png 683w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-8-300x170.png 300w\" sizes=\"auto, (max-width: 683px) 100vw, 683px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And, drumroll please&#8230; <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"573\" height=\"203\" src=\"\/wp-content\/uploads\/2019\/09\/image-9.png\" alt=\"\" class=\"wp-image-333\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-9.png 573w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-9-300x106.png 300w\" sizes=\"auto, (max-width: 573px) 100vw, 573px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">We have a tag now. However, we don&#8217;t have our DeleteBy tag yet. We can solve for this with an Alert rule, that trigger an Automation runbook to set the tag. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s first create an empty runbook. <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"720\" src=\"\/wp-content\/uploads\/2019\/09\/image-10-1024x720.png\" alt=\"\" class=\"wp-image-334\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-10-1024x720.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-10-300x211.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-10-768x540.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-10.png 1194w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s create our runbook later, but make sure it takes in the parameter that the alert rule will send to it. This would look like this. Save and publish this runbook.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"254\" src=\"\/wp-content\/uploads\/2019\/09\/image-15.png\" alt=\"\" class=\"wp-image-339\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-15.png 800w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-15-300x95.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-15-768x244.png 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Afterwards, let&#8217;s create an alert rule for every time a resource group is created. To create this alert rule, head over to the resource group we just created, hit the activity log, and expand the update activity. You should see a link that allows you to create an alert rule:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"281\" src=\"\/wp-content\/uploads\/2019\/09\/image-12-1024x281.png\" alt=\"\" class=\"wp-image-336\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-12-1024x281.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-12-300x82.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-12-768x211.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-12.png 1518w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In the alert rule, change the scope to your full subscription:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"551\" height=\"226\" src=\"\/wp-content\/uploads\/2019\/09\/image-13.png\" alt=\"\" class=\"wp-image-337\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-13.png 551w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-13-300x123.png 300w\" sizes=\"auto, (max-width: 551px) 100vw, 551px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Change the condition to &#8216;create resource group&#8217;:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"529\" src=\"\/wp-content\/uploads\/2019\/09\/image-14-1024x529.png\" alt=\"\" class=\"wp-image-338\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-14-1024x529.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-14-300x155.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-14-768x397.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-14.png 1268w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Then, create a new action group, which will trigger an Automation runbook:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"427\" height=\"1024\" src=\"\/wp-content\/uploads\/2019\/09\/image-16-427x1024.png\" alt=\"\" class=\"wp-image-340\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-16-427x1024.png 427w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-16-125x300.png 125w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-16.png 466w\" sizes=\"auto, (max-width: 427px) 100vw, 427px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"355\" src=\"\/wp-content\/uploads\/2019\/09\/image-17-1024x355.png\" alt=\"\" class=\"wp-image-341\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-17-1024x355.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-17-300x104.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-17-768x266.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-17.png 1839w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Give all of this some names, and create the alert rule:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"1024\" src=\"\/wp-content\/uploads\/2019\/09\/image-18-960x1024.png\" alt=\"\" class=\"wp-image-342\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-18-960x1024.png 960w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-18-281x300.png 281w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-18-768x819.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-18.png 1439w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s test if our rule works as expected, by creating yet another resource group. (btw. it can take up to 5 minutes for the rule to become active, so be patient).<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"798\" height=\"395\" src=\"\/wp-content\/uploads\/2019\/09\/image-19.png\" alt=\"\" class=\"wp-image-343\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-19.png 798w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-19-300x148.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-19-768x380.png 768w\" sizes=\"auto, (max-width: 798px) 100vw, 798px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">If you head on over to your automation runbook &#8211; and you can check whether you got the correct data into your runbook.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"818\" height=\"238\" src=\"\/wp-content\/uploads\/2019\/09\/image-20.png\" alt=\"\" class=\"wp-image-344\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-20.png 818w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-20-300x87.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-20-768x223.png 768w\" sizes=\"auto, (max-width: 818px) 100vw, 818px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"522\" src=\"\/wp-content\/uploads\/2019\/09\/image-21-1024x522.png\" alt=\"\" class=\"wp-image-345\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-21-1024x522.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-21-300x153.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-21-768x392.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-21.png 1267w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">With that done, we can start developing our script to grab the created date, and add an additional tag with the to-delete-by date.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I don&#8217;t want to bother you with writing the script, this is what I ended up with that will add the tag to my resource groups:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Param(\n[object] $WebhookData\n)\n\n$connectionName = \"AzureRunAsConnection\"\ntry\n{\n    # Get the connection \"AzureRunAsConnection \"\n    $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName         \n\n    \"Logging in to Azure...\"\n    Add-AzureRmAccount `\n        -ServicePrincipal `\n        -TenantId $servicePrincipalConnection.TenantId `\n        -ApplicationId $servicePrincipalConnection.ApplicationId `\n        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint \n}\ncatch {\n    if (!$servicePrincipalConnection)\n    {\n        $ErrorMessage = \"Connection $connectionName not found.\"\n        throw $ErrorMessage\n    } else{\n        Write-Error -Message $_.Exception\n        throw $_.Exception\n    }\n}\n\n$WebhookBody = (ConvertFrom-Json -InputObject $WebhookData.RequestBody)\n\n$rgid = $WebhookBody.data.essentials.alertTargetIDs[0]\n$rg = Get-AzureRmResourceGroup -Id $rgid\n$tags=$rg.Tags\nif (-not $tags.Item(\"deleteByDate\"))\n{\n    $createdon = $tags.Item(\"CreatedOnDate\")\n    $date=[datetime]$createdon\n    $deleteday=$date.AddDays(7)\n\n    $tags += @{deleteByDate=$deleteday}\n    Set-AzureRmResourceGroup -Id $rgid -Tag $tags\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">With that code in the runbook, saved and published, we should be able to create a new resource group and get it tagged with a deleteByDate tag. <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"932\" height=\"172\" src=\"\/wp-content\/uploads\/2019\/09\/image-23.png\" alt=\"\" class=\"wp-image-347\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-23.png 932w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-23-300x55.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-23-768x142.png 768w\" sizes=\"auto, (max-width: 932px) 100vw, 932px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Summary of creating a deleteByDate tag<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">I hope you were all able to follow along with what we did here. We are using Azure Policy to add a CreatedOnDate tag. I don&#8217;t have to set this manually, this happens automatically through Azure Policy. Afterwards, we&#8217;re using an Alert Rule in combination with an Automation runbook to generate the deleteByDate tag. With that out of the way, we should be able to create another runbook, that will run daily, to check which resource groups should be deleted.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Adding a daily runbook to check which resources should be deleted<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Next up, we&#8217;ll create a runbook that will check which resource groups need to be deleted. Thinking about this &#8211; we could integrate this in the Logic App we&#8217;ll build next for the approval workflow. As we&#8217;re going to be parsing dates, I believe this is best done in code rather than the graphical Logic Apps designer. Plus, writing this in PowerShell won&#8217;t take long. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s go ahead and create a new Runbook:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"313\" height=\"204\" src=\"\/wp-content\/uploads\/2019\/09\/image-24.png\" alt=\"\" class=\"wp-image-349\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-24.png 313w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-24-300x196.png 300w\" sizes=\"auto, (max-width: 313px) 100vw, 313px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The following code should do the trick of triggering a logic app that will delete all to-be-deleted resource groups:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$connectionName = \"AzureRunAsConnection\"\ntry\n{\n    # Get the connection \"AzureRunAsConnection \"\n    $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName         \n\n    \"Logging in to Azure...\"\n    Add-AzureRmAccount `\n        -ServicePrincipal `\n        -TenantId $servicePrincipalConnection.TenantId `\n        -ApplicationId $servicePrincipalConnection.ApplicationId `\n        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint \n}\ncatch {\n    if (!$servicePrincipalConnection)\n    {\n        $ErrorMessage = \"Connection $connectionName not found.\"\n        throw $ErrorMessage\n    } else{\n        Write-Error -Message $_.Exception\n        throw $_.Exception\n    }\n}\n\n$today=get-date\n$rgs = get-azurermresourcegroup\nforeach ($rg in $rgs) {\n    $tags = $rg.Tags\n    if ($tags){\n        if ($tags.Contains(\"deleteByDate\")){\n            $date = [datetime]$tags.item(\"deleteByDate\")\n            if ($date -lt $today){\n                $rgname = $rg.ResourceGroupName\n                write-output \"We'll be deleting \"\"$rgname\"\"\"\n                #call logic app\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Adding approval workflow through Logic Apps<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now rests us the task of creating a Logic App with the approval workflow built-in. Let do that. In the Azure portal, we&#8217;ll create a new Logic App.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"311\" height=\"488\" src=\"\/wp-content\/uploads\/2019\/09\/image-32.png\" alt=\"\" class=\"wp-image-358\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-32.png 311w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-32-191x300.png 191w\" sizes=\"auto, (max-width: 311px) 100vw, 311px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Once in the designer, we&#8217;ll start with a blank Logic App, and start with the HTTP trigger. If you do not want to figure out the JSON schema yourself, you can use the sample payload to generate the schema.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"617\" height=\"383\" src=\"\/wp-content\/uploads\/2019\/09\/image-34.png\" alt=\"\" class=\"wp-image-360\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-34.png 617w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-34-300x186.png 300w\" sizes=\"auto, (max-width: 617px) 100vw, 617px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Next, we&#8217;ll add an approval e-mail step. Search for that action, select it, and then pair your office 365 mail account.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"620\" height=\"304\" src=\"\/wp-content\/uploads\/2019\/09\/image-35.png\" alt=\"\" class=\"wp-image-361\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-35.png 620w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-35-300x147.png 300w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Afterwards, we&#8217;ll create a condition. This will create a branch, to either delete of do nothing. <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"614\" height=\"199\" src=\"\/wp-content\/uploads\/2019\/09\/image-36.png\" alt=\"\" class=\"wp-image-362\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-36.png 614w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-36-300x97.png 300w\" sizes=\"auto, (max-width: 614px) 100vw, 614px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, in our true branch, we&#8217;ll create a step to delete a Resource Group. This should make our end-to-end Logic App look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"539\" src=\"\/wp-content\/uploads\/2019\/09\/image-37-1024x539.png\" alt=\"\" class=\"wp-image-363\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-37-1024x539.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-37-300x158.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-37-768x405.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-37.png 1139w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s try out our Logic App in isolation first. I&#8217;ll use an app called PostMan (or any other API testing tool you want to use) to try out our Logic App. Once you saved the logic app, you should have gotten a URL in the first step. This is the URL we&#8217;ll need to do our HTTP POST against, but it is not the full url. It doesn&#8217;t contain authentication info and an API-version. To get the actual URL, head over to the main blade of your logic app and hit trigger history. This shows you the full url:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"621\" height=\"484\" src=\"\/wp-content\/uploads\/2019\/09\/image-40.png\" alt=\"\" class=\"wp-image-366\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-40.png 621w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-40-300x234.png 300w\" sizes=\"auto, (max-width: 621px) 100vw, 621px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"349\" height=\"189\" src=\"\/wp-content\/uploads\/2019\/09\/image-41.png\" alt=\"\" class=\"wp-image-367\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-41.png 349w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-41-300x162.png 300w\" sizes=\"auto, (max-width: 349px) 100vw, 349px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">So, we&#8217;ll copy paste this URL into postman, and then add a body. That body should be raw, of type JSON. Put a resource group name in there that may be safely deleted.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"774\" height=\"271\" src=\"\/wp-content\/uploads\/2019\/09\/image-42.png\" alt=\"\" class=\"wp-image-368\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-42.png 774w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-42-300x105.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-42-768x269.png 768w\" sizes=\"auto, (max-width: 774px) 100vw, 774px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This will send us an approval e-mail. Once we hit approve, we should see our Logic App finish and delete our RG:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"614\" height=\"311\" src=\"\/wp-content\/uploads\/2019\/09\/image-39.png\" alt=\"\" class=\"wp-image-365\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-39.png 614w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-39-300x152.png 300w\" sizes=\"auto, (max-width: 614px) 100vw, 614px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This now has successfully deleted our resource group. Let&#8217;s now make our automation runbook of the previous step complete by adding in the code to call our Logic App. I&#8217;ve added the full code of the Runbook again below:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$connectionName = \"AzureRunAsConnection\"\ntry\n{\n    # Get the connection \"AzureRunAsConnection \"\n    $servicePrincipalConnection=Get-AutomationConnection -Name $connectionName         \n\n    \"Logging in to Azure...\"\n    Add-AzureRmAccount `\n        -ServicePrincipal `\n        -TenantId $servicePrincipalConnection.TenantId `\n        -ApplicationId $servicePrincipalConnection.ApplicationId `\n        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint \n}\ncatch {\n    if (!$servicePrincipalConnection)\n    {\n        $ErrorMessage = \"Connection $connectionName not found.\"\n        throw $ErrorMessage\n    } else{\n        Write-Error -Message $_.Exception\n        throw $_.Exception\n    }\n}\n\n$today=get-date\n$rgs = get-azurermresourcegroup\nforeach ($rg in $rgs) {\n    $tags = $rg.Tags\n    if ($tags){\n        if ($tags.Contains(\"deleteByDate\")){\n            $date = [datetime]$tags.item(\"deleteByDate\")\n            if ($date -lt $today){\n                $rgname = $rg.ResourceGroupName\n                write-output \"We'll be deleting \"\"$rgname\"\"\"\n                $url = \"https:\/\/prod-51.northeurope.logic.azure.com:443\/workflows\/8eba1db6ea5140eaa1bf7d7c227aa318\/triggers\/manual\/paths\/invoke?api-version=2016-10-01&amp;sp=%2Ftriggers%2Fmanual%2Frun&amp;sv=1.0&amp;sig=qvQk1RyWOPc3w43ZGRE9R9qUdw9_EUhe8xzRZwacO9s\"\n                $body = \"{\"\"resourcegroupname\"\":\"\"$rgname\"\"}\"\n                Invoke-WebRequest -Uri $url -ContentType \"application\/json\" -Method POST -Body $body -Headers $header -UseBasicParsing\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s test this end-to-end now. For this, we&#8217;ll create a new resource group, and wait about 2 minutes for it to receive it&#8217;s DeleteByDate tag. We&#8217;ll go ahead and manually edit that tag to a date in the past, so we can test our Automation \/ Logic App integration:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"231\" src=\"\/wp-content\/uploads\/2019\/09\/image-43-1024x231.png\" alt=\"\" class=\"wp-image-369\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-43-1024x231.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-43-300x68.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-43-768x173.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-43.png 1361w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">With that done, let&#8217;s trigger out Automation runbook, which will call our Logic App. Just hit start on the runbook, and wait for about a minute for it to finish. And sure enough, I got my approval e-mail in my inbox a minute later:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"553\" height=\"312\" src=\"\/wp-content\/uploads\/2019\/09\/image-44.png\" alt=\"\" class=\"wp-image-370\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-44.png 553w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-44-300x169.png 300w\" sizes=\"auto, (max-width: 553px) 100vw, 553px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Final thing that rests us to do, is tie up this automation runbook with a schedule, so it will quietly run in the background every day.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"616\" height=\"96\" src=\"\/wp-content\/uploads\/2019\/09\/image-45.png\" alt=\"\" class=\"wp-image-371\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-45.png 616w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-45-300x47.png 300w\" sizes=\"auto, (max-width: 616px) 100vw, 616px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I decided to let it run at night at 19:00. <\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"321\" height=\"173\" src=\"\/wp-content\/uploads\/2019\/09\/image-46.png\" alt=\"\" class=\"wp-image-372\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-46.png 321w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-46-300x162.png 300w\" sizes=\"auto, (max-width: 321px) 100vw, 321px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And this should do it. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As there was a bit involved in creating this automation end-to-end, let&#8217;s review our end-to-end workflow to see if we kept this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"306\" src=\"\/wp-content\/uploads\/2019\/09\/image-1-1024x306.png\" alt=\"\" class=\"wp-image-325\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-1-1024x306.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-1-300x90.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-1-768x230.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2019\/09\/image-1.png 1795w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">We first created a policy + automation runbook to tag our resources with a delete date. Then we created a combination of a automation runbook with a Logic App to check those tags and send approval e-mails whether or not we wanted to delete them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The goal of all of this was to create an automated process to delete test infrastructure. It was a bit more involved than I had initially thought, but I&#8217;m fairly happy with the end result. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>A while ago I wrote about a self-destructing VM. The process was completely self-contained, and the VM deletes itself. This was a nice demo &#8211; but not too useful in my day-to-day, where I create demo&#8217;s of all kinds of different resources. In my case it&#8217;d be a lot better to have a daily automation [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-324","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/324","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/comments?post=324"}],"version-history":[{"count":3,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/324\/revisions"}],"predecessor-version":[{"id":373,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/324\/revisions\/373"}],"wp:attachment":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/media?parent=324"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/categories?post=324"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/tags?post=324"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}