https://rjygraham.com/Cloud||GTFO20192019-08-13T23:29:27Zhttps://rjygraham.com/assets/img/nasa-53884-unsplash.jpgMy head is in the cloud.https://rjygraham.com/posts/azure-policy-append-as-gentler-deny.htmlAzure Policy - Using the Append Effect as a More Gentle DenyRyan Graham2019-08-13T13:00:00Z<h1 id="azure-policy-using-the-append-effect-as-a-more-gentle-deny">Azure Policy - Using the Append Effect as a More Gentle Deny</h1>
<p>Recently I've been having a lot of fun working with a customer to operationalize Azure in their enterprise. As these type of engagements go, there's a ton of up-front knowledge transfer followed by rubber meeting the road.</p>
<p>We're firmly in the execution phase and have been leveraging both built-in and custom <a href="https://docs.microsoft.com/en-us/azure/governance/policy/overview">Azure Policy</a> to help enforce cloud controls. If you're not familiar with Azure Policy, here's a brief description from the <a href="https://docs.microsoft.com/en-us/azure/governance/policy/overview">documentation</a>:</p>
<blockquote>
Azure Policy is a service in Azure that you use to create, assign, and manage policies. These policies enforce different rules and effects over your resources, so those resources stay compliant with your corporate standards and service level agreements.
</blockquote>
<p>At the heart of Azure Policy is the <a href="https://docs.microsoft.com/en-us/azure/governance/policy/concepts/definition-structure#policy-rule">policy rule</a> which is essentially an <code>if..then</code> statement:</p>
<pre><code class="language-json">{
"if": {
<condition> | <logical operator>
},
"then": {
"effect": "deny | audit | append | auditIfNotExists | deployIfNotExists | disabled"
}
}
</code></pre>
<h2 id="the-scenario">The Scenario</h2>
<p>Data security is of the utmost importance to all enterprises moving to the cloud -- one misconfiguration and an enterprise could end up having a really, really bad day.</p>
<p>Preventing these types of misconfigurations is where Azure Policy shines as it provides enterprises a means of implementing controls at scale; for example, enforcing encryption at rest and in transit on all Storage Accounts within an enterprise's Azure environment.</p>
<p>In the case of this customer, we wanted to do exactly that -- prevent the possibility of Storage Accounts being created (or updated) without requiring encryption at rest as well as HTTPS for encryption in transit.</p>
<p>With that in mind, we created the following Azure Policy definitions in their Azure environment (both are available in the <a href="https://github.com/Azure/azure-policy">Azure Policy samples on GitHub</a>):</p>
<h4 id="ensure-https-traffic-only-for-storage-account"><a href="https://github.com/Azure/azure-policy/tree/master/samples/Storage/https-traffic-only">Ensure https traffic only for storage account</a></h4>
<pre><code class="language-json">{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"not": {
"field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"equals": "true"
}
}
]
},
"then": {
"effect": "deny"
}
}
</code></pre>
<h4 id="ensure-storage-file-encryption"><a href="https://github.com/Azure/azure-policy/tree/master/samples/Storage/storage-account-file-encryption">Ensure storage file encryption</a></h4>
<pre><code class="language-json">{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"field": "Microsoft.Storage/storageAccounts/enableFileEncryption",
"equals": "false"
}
]
},
"then": {
"effect": "deny"
}
}
</code></pre>
<p>Dissecting the policies above, the <code>if</code> conditions are looking for Azure resource types that are Storage Accounts and have <code>supportsHttpsTrafficOnly</code> or <code>enableFileEncryption</code> property values that are not <code>true</code> or <code>false</code>, respectively (the manner in which these rules are authored matter, more on that below). When <code>allOf</code> these conditions are met the <code>then</code> part of the rule is executed and the policy <code>effect</code> will apply, which in this case is to <code>deny</code> the operation.</p>
<p>To test, we assigned the policies, waited a few minutes for them to take effect, and then attempted to create or update Storage Accounts in a manner that would trigger the <code>deny</code> effect and voila...</p>
<p>Result of the policy denying creation of Storage Account without HTTPS:</p>
<p><img src="../assets/img/azure-policy-create-deny.png" class="img-fluid" alt="Result of the policy denying creation of Storage Account without HTTPS" /></p>
<p>Result of the policy denying the update of an existing Storage Account to HTTP:
<img src="../assets/img/azure-policy-update-deny.png" class="img-fluid" alt="Result of the policy denying the update of Storage Account to HTTP" /></p>
<p>Done and done! Well, almost...</p>
<h2 id="houston-we-have-a-problem">Houston, We Have a Problem</h2>
<p>The great thing about Azure Policy is that like any other code you write, it does EXACTLY what you tell it to do. The bad thing about Azure Policy is that it does EXACTLY what you tell it to do, which can have unintended consequences.</p>
<p>The issue we experienced with the policy rules as written, is the <code>deny</code> effect will block the creation of ANY Storage Account where <code>supportsHttpsTrafficOnly</code> and <code>enableFileEncryption</code> properties are not explicitly set to <code>true</code>. For example, we found this to be problematic with end-users new to Azure creating <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/">VMs</a> via the portal and not understanding why their deployment was failing validation. It's not immediately obvious, but when you walk through the Create a VM wizard, it prompts for the diagnostic Storage Account to use. If you select <code>create new</code>, a Storage Account will be added to the deployment which doesn't explicitly set <code>supportsHttpsTrafficOnly</code> and <code>enableFileEncryption</code>to <code>true</code> and bam, deployment fails validation.</p>
<p><img src="../assets/img/azure-policy-vm-deny.png" class="img-fluid" alt="Unintentional result of the policy denying creation of VM diagnostic Storage Account" /></p>
<p>After taking a longer look at <a href="https://docs.microsoft.com/en-us/azure/governance/policy/concepts/effects">Azure Policy Effects</a>, we settled on reworking the policy rules and changing the <code>deny</code> effects to <code>append</code>. What specifically led us to this decision was the description of the <code>append</code> effect evaluation in the documentation:</p>
<blockquote>
Append evaluates before the request gets processed by a Resource Provider during the creation or updating of a resource. Append adds fields to the resource when the if condition of the policy rule is met. <b>If the append effect would override a value in the original request with a different value, then it acts as a deny effect and rejects the request.</b>
</blockquote>
<p>If the property is missing altogether, the policy will set to the value we have defined. If the property is set to a value that is contrary to the value defined in the policy, then the deployment will be denied. Bingo!</p>
<p>Our re-worked policy rules look like the following:</p>
<h4 id="updated-ensure-https-traffic-only-for-storage-account">Updated Ensure https traffic only for storage account</h4>
<pre><code class="language-json">{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"not": {
"field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"equals": "true"
}
}
]
},
"then": {
"effect": "append",
"details": [
{
"field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"value": "true"
}
]
}
}
</code></pre>
<h4 id="updated-ensure-storage-file-encryption">Updated Ensure storage file encryption</h4>
<pre><code class="language-json">{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"not": {
"field": "Microsoft.Storage/storageAccounts/enableFileEncryption",
"equals": "true"
}
}
]
},
"then": {
"effect": "append",
"details": [
{
"field": "Microsoft.Storage/storageAccounts/enableFileEncryption",
"value": "true"
},
{
"field": "Microsoft.Storage/storageAccounts/encryption.keySource",
"value": "Microsoft.Storage"
}
]
}
}
</code></pre>
<p>After updating the policy rules to the definitions above, our deployment failures caused by the Storage Account properties not being explicitly set were resolved.</p>
<p>A couple things to note:</p>
<ol>
<li>The <code>if</code> part of the <code>enableFileEncryption</code> policy rule needed to be rewritten to look for the condition of not true vs an explicit false - this is critical for these policies to work correctly!</li>
<li>The <code>then</code> effect was updated to be <code>append</code> with the details containing the properties and associated values we want.</li>
</ol>
<p>So there you have it!. When writing your Azure Policy rules, consider using the <code>append</code> effect as a more gentle version of <code>deny</code>.</p>
<p>Hope this helps!</p>
<p>Ryan</p>
<p>Recently I've been having a lot of fun working with a customer to operationalize Azure in their enterprise. As these type of engagements go, there's a ton of up-front knowledge transfer followed by rubber meeting the road.</p>https://rjygraham.com/posts/arm-copyindex-aggregate-outputs.htmlAggregating output values from linked ARM templates in copy operationRyan Graham2019-02-17T14:00:00Z<h1 id="aggregating-output-values-from-linked-arm-templates-in-copy-operation">Aggregating output values from linked ARM templates in copy operation</h1>
<p>Azure Resource Manager templates are a powerful way to describe and automate the creation of Azure services and they're getting more powerful with each release of the service.</p>
<p>I was recently confronted with the challenge of aggregating output values of linked templates deployed in a copy operation so the values could be used in the creation of services later in the template. This post presents a recipe for how you can do the same should you run across this situation - I hope you'll join me.</p>
<h2 id="the-scenario">The Scenario</h2>
<p>As part of a spike I'm working on for a customer, I wanted to create an ARM template that would make it a breeze for them to duplicate what I had done in my subscription and follow along. Without getting too far in the weeds, essentially what I needed to do was deploy a few static Public IPs in various regions and then then grant those IP addresses access to a set of Storage Accounts created by same template by adding the IP addresses to the Storage Accounts' Firewall IP rules.</p>
<p>Whenever possible, the proper way of <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-multiple">deploying more than one instance</a> is to use the copy element which is exactly what I wanted to do this for the Pubic IPs and Storage Accounts in this template. After using a copy for both the Public IPs and Storage Accounts I established that the Storage Accounts <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-define-dependencies">had a dependency</a> on the Public IPs. Great, now I'll just aggregate the Public IP values and pass them into the Storage Account resource definition and call it a day, right? Not quite...</p>
<p>As it turns out, ARM templates don't currently have the native ability to aggregate output from copy operations to be used in dependent resource creation. But surely there must be a way...and it turns out there is!</p>
<h2 id="the-recipe">The Recipe</h2>
<p>After racking my brain for awhile (and an exhaustive search or two later), I stumbled across this <a href="https://stackoverflow.com/questions/53253858/can-one-get-output-from-multiple-copy-copyindex-arm-child-templates">Stack Overflow post</a> which suggested the clever approach of passing the output from a previous copyIndex iteration to the input of the next iteration. Then in the current iteration, you concatenate the input with the current iteration output and return the resulting concatenated array. Rinse, repeat.</p>
<p>However, you'll notice the accepted answer deploys the first instance outside the copy to get an instance of the array and then deploys the remainder inside the copy. I cleaned this up a bit so all instances could be deployed within the copy.</p>
<p>Here's a sample root template:</p>
<pre><code class="language-json">{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"quantity": {
"type": "int",
"defaultValue": 3
}
},
"variables": {
"emptyArray": []
},
"resources": [
{
"apiVersion": "2017-05-10",
"name": "[concat('linked-', copyIndex('exampleCopy', 1))]",
"type": "Microsoft.Resources/deployments",
"copy": {
"name": "exampleCopy",
"count": "[parameters('quantity')]",
"mode": "Serial"
},
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[uri(deployment().properties.templateLink.uri, 'linked.json')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"state": {
"value": "[if(equals(copyIndex('exampleCopy'), 0), variables('emptyArray'), reference(concat('linked-', copyIndex('exampleCopy'))).outputs.state.value)]"
},
"ping": {
"value": "[copyIndex('exampleCopy', 1)]"
}
}
}
}
],
"outputs": {
"aggregate": {
"type": "array",
"value": "[reference(concat('linked-', parameters('quantity'))).outputs.state.value]"
}
}
}
</code></pre>
<p>And the linked template:</p>
<pre><code class="language-json">{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"state": {
"type": "array"
},
"ping": {
"type": "int"
}
},
"variables": {
"pong": "[concat('pong-', parameters('ping'))]"
},
"resources": [],
"outputs": {
"state": {
"type": "array",
"value": "[concat(parameters('state'), array(variables('pong')))]"
}
}
}
</code></pre>
<p>You'll note that in the root template, I've updated the "state" parameter of the linked template deployment with an if function. This function checks to see if the current copyIndex value is 0, and if so, will pass an empty array defined in the variables section otherwise will reference the output from the previous iteration.</p>
<p>I've pushed this sample to GitHub here: <a href="https://github.com/rjygraham/arm-templates/tree/master/src/arm-copyindex-aggregate-outputs">https://github.com/rjygraham/arm-templates/tree/master/src/arm-copyindex-aggregate-outputs</a> which you can run via the following CLI commands or by the Deploy to Azure button (don't worry, no resources are actually created).</p>
<div class="deploy-to-azure">
<a href="https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Frjygraham%2Farm-templates%2Fmaster%2Fsrc%2Farm-copyindex-aggregate-outputs%2Fazuredeploy.json" target="_blank">
<img src="/assets/img/deploytoazure.png" />
</a>
</div>
<pre><code class="language-bash">az group create -g arm-copyindex-aggregate-outputs -l eastus
az group deployment create -g arm-copyindex-aggregate-outputs --template-uri https://raw.githubusercontent.com/rjygraham/arm-templates/master/src/arm-copyindex-aggregate-outputs/azuredeploy.json
</code></pre>
<h2 id="one-caveat">One Caveat</h2>
<p>Please note that for this to work, you'll need to ensure the copy mode is set to <code>Serial</code> for the resources from which you want to aggregate values. This means that in order to minimize deployment times, you'll want to factor your ARM templates such that only the bare minimum is deployed in the linked templates using this method.</p>
<p>Azure Resource Manager templates are a powerful way to describe and automate the creation of Azure services and they're getting more powerful with each release of the service.</p>https://rjygraham.com/posts/hand-drawn-icons.htmlHand-drawn IconsRyan Graham2019-01-23T22:00:00Z<h1 id="hand-drawn-icons">Hand-drawn Icons</h1>
<p>Something you should know about me is that I like to draw the actual Azure service icons when I'm whiteboarding a solution. I find my brain recognizes those glyphs more readily than your standard square or circle. Well, having spent some time revamping my blog I was feeling a bit creative and I thought it might be fun to hand draw some icons I can work into architectural diagrams while writing blog posts.</p>
<p>To commemorate the re-re-re-launch of my blog, I wanted to share these with everyone under the <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>. Feel free to download and use however you see fit. I'd appreciate a shoutout if you use them... and if you think about it, come back here and leave a comment with a link to where you put them to use.</p>
<p>I'll be adding to this collection as needed from my side if they work out the way I intend. Having said that, leave me a comment below if you'd like to see any specific icons. If I have some downtime I'll give it ago.</p>
<p><img src="../assets/img/hand-drawn-tech-icons.gif" class="img-fluid" alt="Hand-drawn Icons" />
(DOWNLOAD: Right-click, save image)</p>
<p>Cheers,
Ryan</p>
<p>Something you should know about me is that I like to draw the actual Azure service icons when I'm whiteboarding a solution. I find my brain recognizes those glyphs more readily than your standard square or circle. Well, having spent some time revamping my blog I was feeling a bit creative and I thought it might be fun to hand draw some icons I can work into architectural diagrams while writing blog posts.</p>https://rjygraham.com/posts/goodbye-ghostblog.htmlGoodbye Ghost, Hello WyamRyan Graham2017-05-21T19:20:00Z<h1 id="goodbye-ghost-hello-wyam">Goodbye Ghost, Hello Wyam</h1>
<p>So it seems as if no matter how committed I am, blogging is just one of those things that unfortunately always gets bumped to the bottom the priority list and utlimately never happens. Because of this, it became difficult for me to justify spending ~$50/mo ($600/yr) running Ghost Blog on Azure and therefore I began searching for lower cost alternatives.</p>
<h3 id="criteria">Criteria</h3>
<p>During my search, I used the following criteria (in no particular order):</p>
<ol>
<li>Azure - Since I know and love Azure, I wanted to continue leveraging it as my hosting platfrom.</li>
<li>Markdown - This was one of the things that originally attractd me to Ghost and something I fell in love with. Markdown really is a glorious language for authoring content.</li>
<li>Drafts/Revisions - I wanted the ability to maintain mulitple drafts and keep track of revisions.</li>
<li>UX - The blog has to look good and also be performant.</li>
<li>Easily Customizable - The blog had to be easily customizable without the need to drudge through the typical CMS/blog cruft. I also prefer a solution that uses C#/Razor since I really enjoy working in those languages.</li>
</ol>
<h3 id="the-framework">The Framework</h3>
<p>After a couple evenings of searching, I happened upon <a href="https://wyam.io">Wyam</a> and immediately knew it had promise. TL;DR - Wyam is a static content generator that has many different "recipes" that can injest markdown and spit out blogs, documentation sites, book sites, etc consisting of static HTML files. I recommend you take a look at the <a href="https://wyam.io">Wyan</a> site for more info.</p>
<h3 id="hosting">Hosting</h3>
<p>Now that I had a potential framework/platform with which I could generate my blog, I began shifting my efforts to think through Azure hosting options that would allow me to minimize costs. Since Wyam generates nothing but a collection of static files, Azure CDN jumped out at me since as the natural fit. The only thing I needed to figure out was how to make extentionless URLs work AND force SSL. I sunk many late nights into figuring out the secret sauce using custom Azure CDN rules... I plan on following this post up with a HOW TO so you can replicate this solution if you'd like.</p>
<h3 id="building">Building</h3>
<p>Finally, I needed to to get the the markdown from my machine, built into static html, and then into Azure Blob storage to be served from Azure CDN. Well it turns out VSTS and continuous integration builds could do exactly that. Using VSTS for source control would also check the box of allowing me to create multiple posts (branches) and track revisions (commits). Again, I'll include all this in my forthcoming HOW TO.</p>
<h3 id="trade-offs">Trade Offs</h3>
<p>The concept laid out above is great for a very simplistic blog like mine; however, it's not perfect. Far from it actually.</p>
<p>For starters, I can't currently schedule blog posts to go live - as soon as the post is merged into master, the CI build will be triggered, and a couple minutes later the post will be live. I have an idea of how I can potentially work around this, but I'm going to need to prototype it out to verify.</p>
<p>Another tradeoff is that I lose the ability to robustly handle 404's and other HTTP errors. Additionally, you can get to pages/posts using URLs with and without extensions. As an example, my original post:</p>
<ul>
<li><a href="https://www.rjygraham.com/posts/More-to-come">https://www.rjygraham.com/posts/more-to-come</a></li>
<li><a href="https://www.rjygraham.com/posts/More-to-come.html">https://www.rjygraham.com/posts/more-to-come.html</a></li>
</ul>
<p>Finally, since the pages are all static, I don't have server-side processing capabilities to handle forms or anything of that nature. I may be able to utilize Azure Functions for something along those lines, but I'll cross that bridge when I get there.</p>
<p>These are all tradeoffs I'm willing to make because I estimate my monthly blog bill will drop from $~50 to < $2. Yes, Blob and CDN are CHEAP!</p>
<p>That's it for now. Stay tuned for a follow-up with the step-by-step of how to do all this.</p>
<p>Cheers,
Ryan</p>
<p>So it seems as if no matter how committed I am, blogging is just one of those things that unfortunately always gets bumped to the bottom the priority list and utlimately never happens. Because of this, it became difficult for me to justify spending ~$50/mo ($600/yr) running Ghost Blog on Azure and therefore I began searching for lower cost alternatives.</p>https://rjygraham.com/posts/automatically-tweet-new-blog-posts.htmlAutomatically tweet new blog posts using Microsoft FlowRyan Graham2016-07-04T15:00:00Z<h1 id="ifttt">IFTTT</h1>
<p>If you're a developer or a technically savvy individual, chances are you've heard of or actually utilize <a href="ifttt.com">If This Then That (IFTTT)</a> to help automate portions of your life.</p>
<p>IFTTT is built upon the notion of recipes which take the form of: if <strong>trigger</strong> then <strong>action</strong>.</p>
<p>Examples of popular recipes include:</p>
<ul>
<li>Automatically keep Facebook and Twitter profile pictures in sync</li>
<li>Automatically post your Instagram pictures to Tumblr</li>
<li>Automatically post Tweets on Facebook when you include a specific hashtag</li>
<li>Etc, etc.</li>
</ul>
<p>As I was installing and configuring my new <a href="https://ghost.org">Ghost blog</a> I noticed it doesn't currently have functionality to automatically tweet new posts on Twitter which is a feature many other blogging platforms offer. Naturally I turned to the wisdom of Twitter and received a fantastic recommendation from David Balderston:</p>
<center><blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr"><a href="https://twitter.com/rjygraham">@rjygraham</a> <a href="https://twitter.com/TryGhost">@TryGhost</a> could use the rss feed for now if you want - <a href="https://t.co/KK6W7sUQSs">https://t.co/KK6W7sUQSs</a></p>— David Balderston (@DavidBalderston) <a href="https://twitter.com/DavidBalderston/status/741369422506393600">June 10, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></center>
<p>And that's exactly what I did using a customized derivative of <a href="https://ifttt.com/recipes/5539-new-entries-to-an-rss-will-be-automatically-tweeted">this recipe</a>. After I little trial and error, I felt confident that the solution was working exactly as advertised.</p>
<h1 id="enter-microsoft-flow">Enter Microsoft Flow</h1>
<p>However, being a Microsoft blue badge and knowing we recently released an IFTTT-like service called <a href="https://flow.microsoft.com">Microsoft Flow</a>, I couldn't leave well enough alone and instead felt compelled to reproduce the IFTTT solution I had just implemented in IFTTT using Microsoft Flow.</p>
<h2 id="getting-started">Getting Started</h2>
<p>To get started with Microsoft Flow, you need to sign-up using an "organizational" account (i.e. an account backed by Azure Active Directory and not merely a Microsoft Account). Since I'm setting up this solution for my personal blog, I didn't want to use my work O365 account which meant there'd be a little bit of work for me to get started.</p>
<p>This is where Azure comes to the rescue. It just so happens to be that I'm actually hosting my Ghost blog in Azure and therefore already have access to an Azure Active Directory in the form of the Default directory created for my Azure subscription. All I needed to do was add a new user in the form of <em>user@yourtenant.onmicrosoft.com</em> to my Azure Active Directory and then return to Flow and complete the sign-up steps, logging in as the newly created user.</p>
<p>From there the experience was much like IFTTT and I was able to quickly create a new flow that would watch my RSS feed and post a tweet when a new feed item was published.</p>
<p>To add the RSS trigger just search for "RSS" or "Feed" in the trigger search box and then plug in URL to your RSS feed:</p>
<p><img src="../assets/img/flow-trigger.GIF" class="img-fluid" alt="RSS Trigger" /></p>
<p>Then click Add an action, search for Twitter, choose Post a new tweet, and then configure the contents of your tweet. <mark>NOTE: If this is your first time using Twitter within Flow, you'll need to establish the connection by authorizing Microsoft Flow to access your Twitter account.</mark></p>
<p><img src="../assets/img/flow-action1.GIF" class="img-fluid" alt="RSS-Twitter Action Take 1" /></p>
<p>Once the trigger and action were linked, I saved the flow, created a new blog post and sat back and waited for a new tweet from me to appear in my timeline. Sure enough, about a minute or two later a shiny new tweet with the title and URL of my blog post appeared like magic. Well, I thought to myself, why stop here?</p>
<h1 id="expanding-the-solution">Expanding The Solution?</h1>
<p>Since this solution worked so well with my personal blog, I wanted to leverage it to post tweets triggered by new blog entries on the <a href="https://azure.microsoft.com/en-us/blog/">Microsoft Azure Blog</a>. Logging back into Microsoft Flow, I quickly duplicated the flow I setup for my personal blog but this time pointed the RSS trigger to the Azure Blog RSS feed. Feeling accomplished, I shut my Surface down for the night and eagerly awaited the Azure team to publish new posts the following day.</p>
<h2 id="trouble-in-paradise">Trouble In Paradise</h2>
<p>Luckily, the Azure team had quite a bit to say the next day which served extremely valuable in highlighting a fatal flaw in my Flow - something I had taken for granted because IFTTT handled it automatically behind the scenes. Turns out my Flow tweeted some of the new Azure blog posts and not others. There didn't seem to be any rhyme or reason for the missing tweets, so I logged into Flow and looked at the failures in the activity log.</p>
<p>Smack. There it was in plain sight:</p>
<p><img src="../assets/img/flow-error.GIF" class="img-fluid" alt="Twitter Error" /></p>
<p>status: 403
message: "Tweet must be 140 characters or less."</p>
<p>Doh! Turns out the combined length of the blog title and URL exceeded Twitter's 140 character limit. The reason this wasn't an issue with IFTTT is because the recipe I had created automatically uses a URL shortener for links and all good bloggers know to keep the titles of posts well under the 140 character limit so the content is easily shareable on Twitter.</p>
<h3 id="post-a-new-tweet-take-2">Post a new tweet - Take 2</h3>
<p>Now that the error was known the solution was fairly straight forward. After evaluating a few URL shortening services I decided to go with Bitly. Although I was familiar with Bitly from having used it in the past, this decision really came down to two technical requirements:</p>
<ul>
<li>Since I'm creating a lightweight "drag and drop" solution I didn't want a complicated authentication/authorization scheme to deal with</li>
<li>Since I didn't want to write code that meant that the shortening service had to return the shortened URL in the body as plain text because otherwise I'd likely have to parse JSON/XML.</li>
</ul>
<p>Bitly came through on both fronts. Per their <a href="http://dev.bitly.com/authentication.html">Authentication</a> API documentation:</p>
<blockquote class="blockquote">
<p>If you only need a token for your own account and will not be authenticating any additional end-users, you can generate a developer access token from <a href="https://bitly.com/a/oauth_apps">https://bitly.com/a/oauth_apps</a> or by using the Basic Authentication Flow</p>
</blockquote>
<p>Also, as illustrated in their <a href="http://dev.bitly.com/links.html#v3_shorten">Links</a> API documentation, by specifying <code>format=txt</code>, the response from Bitly will contain only the shortened URL in the body.</p>
<p>So with that, I was able to add a simple Http action to my flow which would capture the URL of the new blog post from the RSS feed, add it to an HTTP call to Bitly, and finally use the body of the response from Bitly as the link in the Post a new tweet action. The final Flow look like this:</p>
<p><img src="../assets/img/flow-action2.GIF" class="img-fluid" alt="Final Flow" /></p>
<p>I hope this post helps you out or inspires you to check out Microsoft Flow - it's a bit rough around the edges, but there is huge potential. If you have any questions or would like to share any Flows you've created feel free to drop a line in the comments below.</p>
<p>If you're a developer or a technically savvy individual, chances are you've heard of or actually utilize <a href="ifttt.com">If This Then That (IFTTT)</a> to help automate portions of your life.</p>https://rjygraham.com/posts/more-to-come.htmlMore to come...Ryan Graham2016-06-27T23:23:00Z<h1 id="hi-there">Hi there.</h1>
<p>After several false starts throughout my life, I'm finally starting <strong>and</strong> following through with writing a blog. I know, I know, it's easy to say in my first post, but this has been something I've wanted to do for a very long time and I've finally realized that in order to improve myself and my craft, I need to put myself out there.</p>
<blockquote class="blockquote">
<p>"There is only one way to avoid criticism: do nothing, say nothing, and be nothing." ~Aristotle</p>
</blockquote>
<p>After binge reading <a href="http://www.barnesandnoble.com/w/the-phoenix-project-gene-kim/1115141434">The Phoenix Project</a> by <a href="https://twitter.com/RealGeneKim">@RealGeneKim</a> over the weekend, I was reminded of of the <em>improvement kata</em> - a pattern you practice to learn a skill and mindset. My hope that this blog will serve as one form of an improvement kata through the interaction with you.</p>
<p>Buckle up and let's see where this goes. Thanks for coming along for the ride.</p>
<p>After several false starts throughout my life, I'm finally starting <strong>and</strong> following through with writing a blog. I know, I know, it's easy to say in my first post, but this has been something I've wanted to do for a very long time and I've finally realized that in order to improve myself and my craft, I need to put myself out there.</p>