<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Rick Roché</title><link>https://www.rickroche.com/</link><description>Richard Rick Roché; Software Engineer, Solutions Architect, Hiking Enthusiast. A blog mainly about software engineering and software architecture.</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.</copyright><lastBuildDate>Sun, 25 Jun 2023 15:08:33 +0100</lastBuildDate><atom:link href="https://www.rickroche.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Paved Paths Series - Part 5 - The Spectrum of Platform Engineering and Paved Paths</title><link>https://www.rickroche.com/2023/06/paved-paths-series-part-5-the-spectrum/</link><pubDate>Sun, 25 Jun 2023 15:08:33 +0100</pubDate><author>
Richard Roché</author><guid>https://www.rickroche.com/2023/06/paved-paths-series-part-5-the-spectrum/</guid><description><![CDATA[<div class="featured-image">
                <img src="https://www.rickroche.com/2023/06/paved-paths-series-part-5-the-spectrum/the-spectrum-of-platform-engineering-and-paved-paths-cover.png" referrerpolicy="no-referrer">
            </div><div class="details admonition info open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-info-circle fa-fw" aria-hidden="true"></i>Paved Path Series<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content"><p>This is Part 5 in a series of posts exploring Paved Paths between <a href="https://christaceygreen.com/" target="_blank" rel="noopener noreffer">Chris</a>
 and I.</p>
<ul>
<li><a href="https://www.rickroche.com/2023/04/paved-paths-series-part-1-lets-talk-about-paved-paths/" target="_blank" rel="noopener noreffer">Part 1 - Let&rsquo;s Talk About Paved Paths</a>
</li>
<li><a href="https://christaceygreen.com/blog/paved-paths-series-part-2-a-one-pager" target="_blank" rel="noopener noreffer">Part 2 - Paved Paths: A One Pager</a>
</li>
<li><a href="/2023/05/paved-paths-series-part-3-why-paved-paths/" rel="">Part 3 - Why Paved Paths?</a>
</li>
<li><a href="https://christaceygreen.com/blog/paved-paths-series-part-4-the-anatomy-of-paved-paths" target="_blank" rel="noopener noreffer">Part 4 - The Anatomy of Paved Paths</a>
</li>
<li>Part 6 - Measuring the Success of Paved Paths - Coming soon!</li>
</ul></div>
        </div>
    </div>
<p>In this post, we explore the extremes between a centralised model and a completely democratised model, and we suggest a possible nirvana between the two.</p>
<p>Platform Engineering is a hot topic right now - <a href="https://qconlondon.com/" target="_blank" rel="noopener noreffer">QCon London</a>
 had an entire track devoted to it in 2023, <a href="https://platformcon.com/" target="_blank" rel="noopener noreffer">PlatformCon 2023</a>
 saw a massive increase in attendees. It&rsquo;s seen some huge success in places like <a href="https://www.infoq.com/articles/cassandra-kubernetes-microservices/" target="_blank" rel="noopener noreffer">Monzo</a>
, where the shape of a microservice is strongly defined by a platform team, and engineers use a single CLI command to generate their instance on said platform. This model is great&hellip; when it suits your organisation.</p>
<p>Paved Paths (or their similarly named counterparts) have been understandably embraced by the world of Platform Engineering. Using the <a href="https://teamtopologies.com/" target="_blank" rel="noopener noreffer">Team Topologies</a>
 platform topology, we know that the platform needs to provide a compelling internal product to accelerate delivery by Stream-aligned teams. For a platform to succeed, it needs some form of paved paths through it, which can be used to enable self-service by its users.</p>
<p>The problem with this, is that paved paths aren&rsquo;t <strong>only</strong> valid if you&rsquo;re following platform engineering principles. There&rsquo;s a range of approaches when creating paved paths that your organisation could follow, each of which have different benefits and drawbacks. Let&rsquo;s take a look at the spectrum&hellip;</p>
<h2 id="the-spectrum">The Spectrum</h2>
<a class="lightgallery" href="/2023/06/paved-paths-series-part-5-the-spectrum/the-spectrum.png" title="The spectrum of platform engineering and paved paths" data-thumbnail="/2023/06/paved-paths-series-part-5-the-spectrum/the-spectrum.png">
        
    </a>
<h3 id="platform-engineering">Platform Engineering</h3>
<p>At one end of our spectrum, we have Platform Engineering. Engineering teams rely on clearly defined platforms to abstract away many of the &rsquo;things that aren&rsquo;t code&rsquo;. In a lot of cases, this covers the infrastructure containing the application (Kubernetes, Networking, Storage, Monitoring, Resilience etc) as well as common patterns like observability, cost management and security. The engineers focus on the application code, pump out features, and don&rsquo;t bother themselves with things like crafting <a href="https://helm.sh/" target="_blank" rel="noopener noreffer">Helm</a>
 or <a href="https://www.terraform.io/" target="_blank" rel="noopener noreffer">Terraform</a>
 or figuring out observability.</p>
<h3 id="you-build-it-you-run-it-ybiyri">You Build It, You Run It (YBIYRI)</h3>
<p>At the other end of our spectrum, we have YBIYRI. Engineering teams own everything around their application. They write Terraform, or Bicep, or Helm Charts to define their infrastructure. They define the security configuration of their stack. Likewise, they own the resilience and scaling capabilities of their offering, and monitor all of it to ensure it&rsquo;s running just how they expect it to.</p>
<h3 id="paved-paths">Paved Paths</h3>
<p>Sitting firmly in the middle of our extremes, are our paved paths. They&rsquo;re not just a subset of Platform Engineering. They actually provide an intriguing and powerful middle-ground for engineering teams, where we can balance some benefits and drawbacks of the two extremes. This <em>could</em> suit your organisation better. Let&rsquo;s get into why&hellip;</p>
<h2 id="what-do-paved-paths-give-us-here">What do Paved Paths give us here?</h2>
<p>YBIYRI has powerful implications on your people. Engineers will learn huge amounts about what it takes to serve digital products to clients. They&rsquo;ll own their entire Dev(SecFin&hellip;)Ops lifecycle, and start driving the strategy of your technology in ways they never could. This sounds a bit like a beautiful theory, but we&rsquo;ve seen it in practice. Engineers who took this opportunity by the horns (and accepted the surrounding challenges), and thrived.
It&rsquo;s not all sunshine and rainbows though. Delivery can be slow. Cognitive load is high. The risk of burnout is always just over your shoulder, lurking among the many unknowns being juggled.</p>
<p>You may think Platform Engineering is the only escape from these issues. Abstract it all away. Your engineers only need to care about the code, and the platforms will take care of everything else. However, we need not jump to that extreme. Paved Paths offer us a great helping hand, without hiding innovation opportunities and growth from our engineers. Build paved paths, almost as if you had a platform underneath, but following a YBIYRI model. Engineering teams still own everything, but their cognitive load is reduced by commonly trodden, high-quality paths. Engineers can break away and innovate where they see value, but they still just use a wheel when they need a wheel.</p>
<h2 id="risks">Risks</h2>
<p>There are risks with this approach. Abstracting away complexity, without a platform team taking ownership of it, introduces a potential gap. At 2am, the engineering team is still expected to be able to debug an issue, they have no-one else to lean on. Mitigating this risk should mostly be a culture thing. A culture of continuous learning, where teams are encouraged and passionate about understanding their digital products, should fill this gap. Yes, engineers utilise paved paths to deliver faster, but they don&rsquo;t live in ignorance of what happens &lsquo;underneath&rsquo;. Their architecture and tech stack is their own, paved paths simply accelerate their delivery. Obviously, the best culture in the world won&rsquo;t solve this alone, great documentation and a great developer experience is vital. But as long as you&rsquo;ve read our <a href="https://christaceygreen.com/blog/paved-paths-series-part-4-the-anatomy-of-paved-paths" target="_blank" rel="noopener noreffer">Part 4</a>
, you should be in a solid position there!</p>
<h2 id="ownership">Ownership</h2>
<p>By sitting ourselves in the centre of this spectrum, we raise a new question over ownership of the Paved Paths. As we&rsquo;ve discussed, no specific team owns them. There isn&rsquo;t a platform team to rely on, and the engineering teams own their products. So how do we maintain them, and ensure they are still valuable to the community? Well, community engagement is a must. If your organisation is running quality production software, it already has the skills and subject-matter experts (SMEs) needed to maintain an incredible set of Paved Paths. They just need to be empowered and encouraged to own building blocks within them.</p>
<p>Engineering teams should contribute back to the Paved Paths they rely on, evolving them as they learn from production. They are the best informed on the state of the developer experience and are best placed to improve it. Encourage your teams to make time to contribute back and normalise taking time for continuous improvement.</p>
<p>Empower the SMEs in your organisation to contribute and own components of your paved paths. DBAs can pour their deep knowledge into sections relevant to databases, driving performance and cost-saving improvements across the organisation. Infrastructure experts can apply their expertise to areas like Infrastructure-as-Code, Networking, and Automation techniques. Security SMEs can take their standards and recommendations, and implement them within building blocks and pipeline tooling. For this to work, an <a href="https://about.gitlab.com/topics/version-control/what-is-innersource/" target="_blank" rel="noopener noreffer">innersourcing</a>
 model is a great idea - make this part of your paved path contribution model, and make the contribution guidelines clear.</p>
<p>Organisational structures don&rsquo;t need to change, if the right people are brought together and sponsored by leaders to contribute. As well as the organisational benefits, this is also incredibly attractive to SMEs. Rather than producing documents of standards, and then becoming a gate at some point in the delivery process, SMEs get to see their work &rsquo;live&rsquo;, being part of the solution rather than being a gate that is seen as slowing teams down.</p>
<p>There&rsquo;s one more group of people who can provide a huge amount of value towards Paved Paths when living on this spectrum. Enablement teams. We&rsquo;re not going to go into detail of what enablement teams are (maybe a future post outside this series!), but a key area of their contribution is towards these Paved Paths. By being well-connected to their engineering teams, enablement folks are incredibly well-placed to inform on the requirements of all building blocks, whilst also encouraging alignment and fast feedback loops. The team should not own them alone, but instead integrate the engineering community and SMEs with a clear strategy of the paths the organisation requires at that point in time.</p>
<div class="details admonition note open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-pencil-alt fa-fw" aria-hidden="true"></i>Is my organisation big enough for this?<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content"><p>Absolutely! The size of an organisation will only affect the number of people you may need to scale out your engineering practices. Either side of the spectrum focuses effort in different ways, but doesn&rsquo;t dictate more or less people.</p>
<p>The most important factor here is sponsorship and focus. An organisation sponsoring long-term value, rather than short-term wins will succeed (see <a href="https://christaceygreen.com/blog/paved-paths-series-part-4-the-anatomy-of-paved-paths#culture" target="_blank" rel="noopener noreffer">culture - part 4</a>
).</p>
</div>
        </div>
    </div>
<p>Up next, in part 6, we will discuss the important topic of measuring the success of your paved paths - coming soon!</p>
<p>Featured image background generated by <a href="https://www.bing.com/create" target="_blank" rel="noopener noreffer">Bing Image Creator</a>
, updated using <a href="https://github.com/excalidraw/excalidraw" target="_blank" rel="noopener noreffer">excalidraw</a>
.</p>
]]></description></item><item><title>Paved Paths Series - Part 3 - Why Paved Paths?</title><link>https://www.rickroche.com/2023/05/paved-paths-series-part-3-why-paved-paths/</link><pubDate>Sat, 06 May 2023 10:43:59 +0100</pubDate><author>
Richard Roché
and
Chris Tacey-Green</author><guid>https://www.rickroche.com/2023/05/paved-paths-series-part-3-why-paved-paths/</guid><description><![CDATA[<div class="featured-image">
                <img src="https://www.rickroche.com/2023/05/paved-paths-series-part-3-why-paved-paths/why-paved-paths-cover.png" referrerpolicy="no-referrer">
            </div><div class="details admonition info open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-info-circle fa-fw" aria-hidden="true"></i>Paved Path Series<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content"><p>This is Part 3 in a series of posts exploring Paved Paths between <a href="https://christaceygreen.com/" target="_blank" rel="noopener noreffer">Chris</a>
 and I.</p>
<ul>
<li><a href="https://www.rickroche.com/2023/04/paved-paths-series-part-1-lets-talk-about-paved-paths/" target="_blank" rel="noopener noreffer">Part 1 - Let&rsquo;s Talk About Paved Paths</a>
</li>
<li><a href="https://christaceygreen.com/blog/paved-paths-series-part-2-a-one-pager" target="_blank" rel="noopener noreffer">Part 2 - Paved Paths: A One Pager</a>
</li>
<li><a href="https://christaceygreen.com/blog/paved-paths-series-part-4-the-anatomy-of-paved-paths" target="_blank" rel="noopener noreffer">Part 4 - The Anatomy of Paved Paths</a>
</li>
<li><a href="/2023/06/paved-paths-series-part-5-the-spectrum/" rel="">Part 5 - The Spectrum of Platform Engineering and Paved Paths</a>
</li>
<li>Part 6 - Measuring the Success of Paved Paths - Coming soon!</li>
</ul></div>
        </div>
    </div>
<p>In this post we explore why organisations should care about paved paths.</p>
<h2 id="what-problems-do-paved-paths-solve">What problems do paved paths solve?</h2>
<p>There is a tension that is constantly at play in software engineering teams - increase their autonomy to enable them to build without hand-offs, while reducing the same teams&rsquo; cognitive load to allow them to focus on delivering value. With cloud computing, shifting left on everything, and maturing practices that enable continuous delivery, teams now have the potential to build, run and operate every aspect of their applications. But with this potential, comes the very real threat of burnout, skill gaps that lead to poor quality products, unwanted risks or slow time to market.</p>
<p>We&rsquo;ve experienced both sides of this dichotomy - so let us quickly explore what each extreme looks like from our lived experience &hellip;</p>
<h3 id="centralised-capabilities">Centralised Capabilities</h3>
<p>A centralised team (or many of them) abstract away areas of the tech stack from delivery teams. &ldquo;Our engineers shouldn&rsquo;t need to know where their app is hosted&rdquo;. This reduces the cognitive load of the delivery team, which is great. Unfortunately, the delivery team now becomes hugely frustrated by a complete lack of autonomy, needing to log many tickets to the centralised team who have accidentally become a bottleneck. The delivery team also find that they struggle to make the centralised capabilities cover all of their requirements as these are constantly evolving. It&rsquo;s rigid, but they can&rsquo;t use anything else. Similarly, the centralised team becomes frustrated with an overwhelming number of tickets to complete and is not able to focus on creating self-service options.</p>
<h3 id="decentralised-capabilities">Decentralised Capabilities</h3>
<p>Delivery teams own their entire stack. Amazing! Their quest for maximum autonomy has been achieved, and now they can finally get down to delivering features, without blockers from those pesky centralised teams. Except, they now wade through increasing cognitive load, solving the same problems in multiple places, often unaware that 3 other teams solved the problem 6 months ago. Similarly, the centralised team is now disempowered and watches on as more and more inconsistency spreads.</p>
<p>Clearly, neither extreme is a great place to be as an organisation. Fortunately, this is a problem that Paved Paths can solve well - balancing team autonomy without overwhelming teams with too much cognitive load while enabling self-service capabilities.</p>
<h3 id="does-this-apply-to-all-organisations">Does this apply to all organisations?</h3>
<p>Nope. Much like deciding when to build a monolith and when to split into microservices, building paved paths isn&rsquo;t going to help some sizes and shapes of organisations. If you&rsquo;re a startup with 5 engineers (and ChatGPT), you&rsquo;re unlikely to see the benefits of paved paths. Your alignment and quality assurance comes from sitting together 12 hours a day, working on the same repository, and having solid PR reviews.</p>
<p>Even as you grow, and have 3 separate engineering teams working on separate microservices, you can probably live without paved paths.</p>
<p>So how do you know you&rsquo;ve reached the size or shape where paved paths are going to be a valuable investment? To answer this, you need to have a pulse on your tech stack and engineering practices. You&rsquo;ll start to see certain things popping up across your organisation. Sprawl of tooling. Inconsistent patterns. Engineer frustration. A slowing rate of change, or a higher rate of production failures. These are your signals. Your organisation has too many hikers fighting bears in the woods, and a paved path or two would really come in handy.</p>
<h2 id="why-invest-in-paved-paths">Why invest in paved paths?</h2>
<div class="details admonition note open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-pencil-alt fa-fw" aria-hidden="true"></i>Let&#39;s talk money<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">Commercially, why do we care? Well, for a second let&rsquo;s ignore employee satisfaction and focus on our clients. We want to deliver great quality features to them, quickly. Our clients don&rsquo;t care about paved paths, they just want to see an incredible product with continuously improving and evolving feature sets. And with the rate that the technical landscape is changing right now, our products need to be able to iterate extremely quickly. To achieve this, our organisations should optimise for fast flow. We need to reduce the time it takes to go from &ldquo;Hey, I&rsquo;ve got an idea&hellip;&rdquo; to &ldquo;It&rsquo;s live!&rdquo;. This is what Paved Paths are designed for. They remove the weeks of debate, design (and possible disaster) from our delivery path and empower teams to experiment with and deploy features quickly.
<strong><em>Great ideas + Fast flow = $$$</em></strong></div>
        </div>
    </div>
<p>Paved Paths provide a codified home for all of your learnt best practices, for the things you want to standardise on, for ensuring that things like security, observability and other common components are baked into solutions and only need to be solved once. They also need to be constantly evolving and the usage of them becomes a necessary feedback loop.</p>
<p>In modernisation projects that we&rsquo;ve worked on before, there have always been reference implementations built that aim to show teams how something could work. They do provide <em>some</em> value. Teams can happily find them, copy-paste, and pick apart to suit their use case. The trouble with reference implementations is that they are generally only good for a specific point in time. If we were to breathe life into all of our documentation, reference implementations and written standards (with a bias towards enabling fast flow) we would start to get the essence of Paved Paths.</p>
<p>Paved paths are more than reference implementations, they are the building blocks in any internal developer platform - empowering teams to get up and running using well-understood design patterns with tried and tested technologies. They aim to provide a standardised experience that solves for common use cases, covering infrastructure, applications and operations.</p>
<div class="details admonition note open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-pencil-alt fa-fw" aria-hidden="true"></i>A brief rant about Standards<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">For us, mentioning &ldquo;standards&rdquo; has the power to make us cringe so hard we bite our own teeth. They&rsquo;re not inherently bad things. They tend to come from a good place. The problem is that they tend to be entirely detached from the reality of an engineer&rsquo;s world. They live in a document somewhere, officially stamped with approval by experts and are then left to collect dust. Paved Paths are one of the few genuinely effective solutions to this problem, because they give your standards <strong>life</strong>. Your engineers are actively applying your standards because they&rsquo;re now present in their day-to-day.
Make this a rule: <strong>if standards aren&rsquo;t part of your paved paths, they don&rsquo;t exist</strong>.</div>
        </div>
    </div>
<p>Invest in paved paths if you would like</p>
<ul>
<li>To make doing the &ldquo;right thing&rdquo; also the easiest thing, enabling clean architecture</li>
<li>To increase team autonomy while reducing cognitive load</li>
<li>To provide self-service, composable building blocks</li>
<li>To solve common problems once that the 80% are going to experience</li>
</ul>
<p>Additionally,</p>
<ul>
<li>They should be optional for teams to use, to enable experimentation where appropriate</li>
<li>They are designed to provide the best user experience for engineers (DX)</li>
<li>They should be secure by design</li>
<li>They help you implement guard rails, while reducing gates</li>
</ul>
<p>In a perfect world, teams would never have to learn the same lesson more than once in your organisation. Paved paths form the home where this shared consciousness is stored, iterated on and shared.</p>
<p>Up next, in part 4, we will unpack the anatomy of Paved Paths and all the components we believe should go into them - read it <a href="https://christaceygreen.com/blog/paved-paths-series-part-4-the-anatomy-of-paved-paths" target="_blank" rel="noopener noreffer">here</a>
.</p>
<p>Featured image background by <a href="https://unsplash.com/pt-br/@meganlee007?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreffer">Megan Lee</a>
 on <a href="https://unsplash.com/photos/EsNA2bhBZ-8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreffer">Unsplash</a>
</p>
]]></description></item><item><title>Paved Paths Series - Part 1 - Let's Talk About Paved Paths</title><link>https://www.rickroche.com/2023/04/paved-paths-series-part-1-lets-talk-about-paved-paths/</link><pubDate>Sun, 30 Apr 2023 12:00:00 +0100</pubDate><author>
Richard Roché
and
Chris Tacey-Green</author><guid>https://www.rickroche.com/2023/04/paved-paths-series-part-1-lets-talk-about-paved-paths/</guid><description><![CDATA[<div class="featured-image">
                <img src="https://www.rickroche.com/2023/04/paved-paths-series-part-1-lets-talk-about-paved-paths/lets-talk-about-paved-paths-cover.png" referrerpolicy="no-referrer">
            </div><div class="details admonition info open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-info-circle fa-fw" aria-hidden="true"></i>Paved Path Series<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content"><p>This is Part 1 in a series of posts exploring Paved Paths between <a href="https://christaceygreen.com/" target="_blank" rel="noopener noreffer">Chris</a>
 and I.</p>
<ul>
<li><a href="https://christaceygreen.com/blog/paved-paths-series-part-2-a-one-pager" target="_blank" rel="noopener noreffer">Part 2 - Paved Paths: A One Pager</a>
</li>
<li><a href="/2023/05/paved-paths-series-part-3-why-paved-paths/" rel="">Part 3 - Why Paved Paths?</a>
</li>
<li><a href="https://christaceygreen.com/blog/paved-paths-series-part-4-the-anatomy-of-paved-paths" target="_blank" rel="noopener noreffer">Part 4 - The Anatomy of Paved Paths</a>
</li>
<li><a href="/2023/06/paved-paths-series-part-5-the-spectrum/" rel="">Part 5 - The Spectrum of Platform Engineering and Paved Paths</a>
</li>
<li>Part 6 - Measuring the Success of Paved Paths - Coming soon!</li>
</ul></div>
        </div>
    </div>
<p>Paved Paths. Golden Paths. Paved Roads. You&rsquo;d be forgiven for thinking the industry already had these nailed down, a solved problem that has been spoken about for years in various guises. With the current spotlight on platform engineering and developer experience, their name is being comfortably thrown around more and more regularly. Unfortunately, descriptions of them are usually from 30,000ft. and if you try to delve deeper, looking for details and commonly experienced challenges, you&rsquo;ll likely find very little. Maybe a few gems hidden in the clouds&hellip;</p>
<p>With this series of blog posts, we would like to change that.</p>
<p><a href="https://christaceygreen.com/" target="_blank" rel="noopener noreffer">Chris</a>
 and <a href="https://www.rickroche.com/" target="_blank" rel="noopener noreffer">I</a>
 have worked together previously and are both passionate about engineering enablement. Recently we ran into each other at a conference and over a great lunch and a lot of conversation, we spoke about quantifying what paved paths are exactly, how we can make them better and what intentional steps can be taken to allow them to be successful. We realised, that in our professional capacities, this is a problem we&rsquo;ve been in the detail of, learnt lessons and carried on without reflecting and writing them down. Through this series, we will hopefully demystify these incredibly useful concepts and add our insights with some real-world experience.</p>
<p>Our motivators for writing this series:</p>
<ul>
<li>Paved Paths are usually bundled in with a centralised platform engineering model. We&rsquo;re both passionate about them being equally valuable with a decentralised approach.</li>
<li>Paved Paths are hugely important to the industry with varying interpretations of what they are. They&rsquo;re not all sunshine and rainbows, but we&rsquo;d love to see more people apply them successfully.</li>
<li>We are continuously asking our engineers to take on more and more responsibilities within the DevOps lifecycle as we shift left. This can ease their pain.</li>
<li>Most examples are embedded in the Kubernetes ecosystem (where there is a lot of phenomenal tooling). We&rsquo;d like to spotlight that Paved Paths are entirely possible outside of k8s, whether using PaaS or on-prem servers!</li>
</ul>
<div class="details admonition note open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-pencil-alt fa-fw" aria-hidden="true"></i>What&#39;s in a name?<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">Paved paths are also known as golden paths or paved roads. The term paved paths makes us think of hiking trails that evolve over time, with well-trodden paths being upgraded / maintained to be easier and safer to use as time goes on. We will use this term going forwards as it resonates best with us.</div>
        </div>
    </div>
<p>These are some topics we would like to explore in this series:</p>
<ul>
<li>The elusive one-pager that every CxO needs to kickstart your journey</li>
<li>The problems we are solving for and why it is a good idea to invest time in paved paths</li>
<li>What makes up a paved path?</li>
<li>To centralise or decentralise?</li>
<li>What is important to measure?</li>
<li>Practical ways to get started</li>
</ul>
<p>We hope you will enjoy these as much as we&rsquo;ve enjoyed exploring them. Start a conversation with us on Twitter - I&rsquo;m <a href="https://twitter.com/rickroche_" target="_blank" rel="noopener noreffer">@rickroche_</a>
 and Chris is <a href="https://twitter.com/ctaceygreen" target="_blank" rel="noopener noreffer">@ctaceygreen</a>
.</p>
<p>In part 2 of our Paved Path Series we start off with a one-pager to help you explain this to your CxO - read it <a href="https://christaceygreen.com/blog/paved-paths-series-part-2-a-one-pager" target="_blank" rel="noopener noreffer">here</a>
!</p>
<p>Images generated by <a href="https://www.craiyon.com/" target="_blank" rel="noopener noreffer">Craiyon</a>
, updated using <a href="https://github.com/excalidraw/excalidraw" target="_blank" rel="noopener noreffer">excalidraw</a>
.</p>
]]></description></item><item><title>Single Sign-On, Azure Static Web Apps and Azure Active Directory</title><link>https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/</link><pubDate>Tue, 01 Mar 2022 12:00:00 +0200</pubDate><author>
Richard Roché</author><guid>https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/</guid><description><![CDATA[<div class="featured-image">
                <img src="https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/azure-static-web-apps-sso-cover.png" referrerpolicy="no-referrer">
            </div><p>We often build and deploy web applications specifically for users internal to our organisation. <a href="https://azure.microsoft.com/en-us/services/app-service/static/" target="_blank" rel="noopener noreffer">Azure Static Web Apps</a>
 is proving to be an excellent replacement for <a href="https://azure.microsoft.com/en-us/services/app-service/" target="_blank" rel="noopener noreffer">Azure App Service</a>
 in these scenarios.</p>
<p>At a high-level the service provides you with a great set of features (outlined in the <a href="https://azure.microsoft.com/en-us/updates/azure-static-web-apps-is-now-generally-available/" target="_blank" rel="noopener noreffer">Azure release notes</a>
)</p>
<blockquote>
<ul>
<li>Globally distributed content for production apps</li>
<li>Tailored CI/CD workflows from code to cloud</li>
<li>Auto-provisioned preview environments</li>
<li>Custom domain configuration and free SSL certificates</li>
<li>Built-in access to a variety of authentication providers</li>
<li>Route-based authorization</li>
<li>Custom routing</li>
<li>Integration with serverless APIs powered by Azure Functions</li>
<li>A custom Visual Studio Code developer extension</li>
<li>A feature-rich CLI for local development</li>
</ul>
</blockquote>
<h2 id="the-desired-experience">The desired experience?</h2>
<p>The experience I wanted to achieve was that if one of our internal users browsed to any of our internal apps, they would be able to use SSO across them provided they were a member of the AAD group needed to access the app (or just a member of our tenant for organisation-wide apps) - no login button, just a seamless logged-in user experience.</p>
<p>It turns out this was super easy to get right! Follow below!</p>
<h2 id="authentication-options">Authentication options</h2>
<p>Azure Static Web Apps makes authentication easy to enable across the three <a href="https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-authorization?tabs=invitations" target="_blank" rel="noopener noreffer">pre-configured identity providers</a>
</p>
<ul>
<li>Azure Active Directory (AAD)</li>
<li>Github or</li>
<li>Twitter</li>
</ul>
<p>These options allow users to login using a login button linking to the desired provider.</p>
<p>Initially I tried to use the pre-configured AAD provider, and when trying to log in using my company account I was presented with this approval dialogue</p>
<a class="lightgallery" href="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-swa-admin-approval.png" title="Azure Static Web Apps Admin Approval" data-thumbnail="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-swa-admin-approval.png">
        
    </a>
<p>Meaning if I was able to get an admin to grant the permission, all users from our tenant would be able to log in to all Azure Static Web Apps, regardless of who had deployed them making this a non-starter for me.</p>
<p>Fortunately we already deploy our static web apps using the Standard plan for <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/static/" target="_blank" rel="noopener noreffer">$9/per app/month</a>
, giving us the ability to use <a href="https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-custom?tabs=aad" target="_blank" rel="noopener noreffer">custom authentication</a>
 and use SSO with our organisations AAD tenant, internal app registrations and restrict access to groups / users in our tenants directory.</p>
<h2 id="getting-it-done">Getting it done!</h2>
<a class="lightgallery" href="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/c4-container.png" title="C4 Container Diagram" data-thumbnail="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/c4-container.png">
        
    </a>
<p>To achieve the desired experience there are a number of components required</p>
<ul>
<li>the static web app (the website)</li>
<li>an Azure App Registration for your app in your tenant</li>
<li>an Azure resource group for your project (I&rsquo;m working on the assumption you already have a subscription, if not <a href="https://docs.microsoft.com/en-us/azure/cost-management-billing/manage/create-subscription" target="_blank" rel="noopener noreffer">read here</a>
)
<ul>
<li>an Azure Static Web App for the web app</li>
<li>an Azure Key Vault to safely store the secrets for your app</li>
</ul>
</li>
</ul>
<p>I will be showing you how to create, configure and deploy these using <a href="https://docs.github.com/en/actions" target="_blank" rel="noopener noreffer">GitHub Actions</a>
, <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep" target="_blank" rel="noopener noreffer">Bicep</a>
 (for creating the Azure resources) and some once off scripts.</p>
<div class="details admonition info open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-info-circle fa-fw" aria-hidden="true"></i>Info<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">All code is available on GitHub <a href="https://github.com/rick-roche/azure-static-web-apps-sso" target="_blank" rel="noopener noreffer">over here</a></div>
        </div>
    </div>
<div class="details admonition note open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-pencil-alt fa-fw" aria-hidden="true"></i>Note<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">It is a good idea to wire up your Key Vault to a Log Analytics workspace or similar to track audit events when in production.</div>
        </div>
    </div>
<h2 id="initial-setup-and-deployment">Initial setup and deployment</h2>
<p>First we are going to scaffold our web app, define our infrastructure and deploy to Azure without any auth in place. Once done, we will move onto the configuration of the app with auth.</p>
<h3 id="scaffold-the-web-app">Scaffold the web app</h3>
<p>For the purposes of this post, I needed a simple static web app and used the <a href="https://kit.svelte.dev/" target="_blank" rel="noopener noreffer">SvelteKit</a>
 skeleton project with the <a href="https://github.com/sveltejs/kit/tree/master/packages/adapter-static" target="_blank" rel="noopener noreffer">static adapter</a>
.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm init svelte@next webapp
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> webapp
</span></span><span class="line"><span class="cl">npm install --save-dev @sveltejs/adapter-static@next
</span></span></code></pre></td></tr></table>
</div>
</div><p>You need to update the default <code>svelte.config.js</code> to <a href="https://github.com/sveltejs/kit/tree/master/packages/adapter-static#usage" target="_blank" rel="noopener noreffer">use the static adapter</a>
, see an example <a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/webapp/svelte.config.js" target="_blank" rel="noopener noreffer">here</a>
.</p>
<h3 id="define-the-azure-infrastructure">Define the Azure Infrastructure</h3>
<h4 id="resource-group">Resource Group</h4>
<p>First, create an Azure resource group. For this tutorial I&rsquo;m creating it manually from my terminal and <a href="https://docs.microsoft.com/en-us/cli/azure/" target="_blank" rel="noopener noreffer">Azure CLI</a>
</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">az group create -g rg-swa-sso -l northeurope
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="resources">Resources</h4>
<p>We will be deploying a Static Web App as well as a Key Vault using Bicep. I&rsquo;ve split out the Bicep files to make them easier to work with as follows (filenames link to the code on GitHub)</p>
<ul>
<li><a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/main.bicep" target="_blank" rel="noopener noreffer"><code>main.bicep</code></a>
 - the main template that is deployed which makes use of the other templates</li>
<li><a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/get-kv-secrets-refs.bicep" target="_blank" rel="noopener noreffer"><code>get-kv-secrets-refs.bicep</code></a>
 - a helper to build up the Key Vault secret references</li>
<li><a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/key-vault.bicep" target="_blank" rel="noopener noreffer"><code>key-vault.bicep</code></a>
 - defines the Key Vault resources and the role assignments needed</li>
<li><a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/static-sites.bicep" target="_blank" rel="noopener noreffer"><code>static-sites.bicep</code></a>
 - defines the Static Web App resources needed</li>
</ul>
<p>In the <a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/main.bicep" target="_blank" rel="noopener noreffer"><code>main.bicep</code></a>
 there are the following highlights to make note of</p>
<ul>
<li>Configuring the static web app
<ul>
<li>to have app settings that are Key Vault references (<code>AAD_CLIENT_ID</code>, <code>AAD_CLIENT_SECRET</code>)</li>
<li>to use the <code>Standard</code> sku</li>
</ul>
</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">swa</span><span class="w"> </span><span class="s">&#39;static-sites.bicep&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;deploy-swa-</span><span class="si">${</span><span class="nv">appName</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">appSettings</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">AAD_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="nv">refs</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">aadClientIdRef</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">AAD_CLIENT_SECRET</span><span class="p">:</span><span class="w"> </span><span class="nv">refs</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">aadClientSecretRef</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">sku</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Standard&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">tier</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Standard&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>And configuring the key vault to allow the static web app permissions to read from it</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="c1">// https://docs.microsoft.com/en-us/azure/key-vault/general/rbac-guide?tabs=azure-cli#azure-built-in-roles-for-key-vault-data-plane-operations</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">var</span><span class="w"> </span><span class="nv">keyVaultSecretsUserRole</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&#39;4633458b-17de-408a-b874-0445c86b69e6&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">module</span><span class="w"> </span><span class="nv">kv</span><span class="w"> </span><span class="s">&#39;key-vault.bicep&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;deploy-kv-</span><span class="si">${</span><span class="nv">appName</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">roleAssignments</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">roleDefinitionId</span><span class="p">:</span><span class="w"> </span><span class="nv">keyVaultSecretsUserRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">principalType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;ServicePrincipal&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">principalId</span><span class="p">:</span><span class="w"> </span><span class="nv">swa</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">siteSystemAssignedIdentityId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="deploy-using-github">Deploy using GitHub</h3>
<h4 id="deployment-credentials">Deployment credentials</h4>
<p>For a GitHub Action to be able to deploy to your resource group you need to have some form of deployment credentials. You can read about the options available <a href="https://docs.microsoft.com/en-us/azure/app-service/deploy-github-actions?tabs=userlevel" target="_blank" rel="noopener noreffer">here</a>
. I&rsquo;ll be using the <a href="https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli" target="_blank" rel="noopener noreffer">Service Principal</a>
 approach with <a href="https://docs.microsoft.com/en-us/cli/azure/" target="_blank" rel="noopener noreffer">Azure CLI</a>
.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">az ad sp create-for-rbac --name <span class="s2">&#34;sp-swa-sso&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --role Owner <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --scopes /subscriptions/<span class="o">{</span>subscription-id<span class="o">}</span>/resourceGroups/<span class="o">{</span>resource-group-name<span class="o">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --sdk-auth
</span></span></code></pre></td></tr></table>
</div>
</div><p>You will get a JSON output from this that you can then save as a GitHub secret with the name <code>AZURE_CREDENTIALS</code>.</p>
<div class="details admonition note open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-pencil-alt fa-fw" aria-hidden="true"></i>Note<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content"><ul>
<li>Replace <code>{subscription-id}</code> with the ID of your subscription</li>
<li>Replace <code>{resource-group-name}</code> with the name of your resource group</li>
<li>I&rsquo;ve made the service principal have Owner access to the resource group (so that I can assign roles).</li>
</ul>
</div>
        </div>
    </div>
<h4 id="workflow-token">Workflow token</h4>
<p>Azure Static Web Apps needs access to your workflow when deploying. For this we&rsquo;ll set up a <code>WORKFLOW_TOKEN</code> secret using a GitHub personal access token with the workflow scope. Follow <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token" target="_blank" rel="noopener noreffer">the instructions here</a>
 to create the token.</p>
<h4 id="github-action-workflow">Github Action Workflow</h4>
<p>With the above secrets in place, we can now create a workflow (mines in <a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/6c95cffac91b8d04aefda1921683e03fda5f51bf/.github/workflows/deploy.yaml" target="_blank" rel="noopener noreffer"><code>.github/workflows/deploy.yaml</code></a>
) which</p>
<ul>
<li>specifies some environment variables for names, tags and locations</li>
<li>checks out the repo</li>
<li>logs into Azure using <code>${{ secrets.AZURE_CREDENTIALS }}</code></li>
<li>deploys to the resource group using the <a href="https://github.com/marketplace/actions/azure-cli-action" target="_blank" rel="noopener noreffer"><code>azure/CLI@v1</code> action</a>
</li>
<li>gets the API from the deployed static web app (so that we can deploy code to it)</li>
<li>deploys the webapp using the <a href="https://github.com/Azure/static-web-apps-deploy" target="_blank" rel="noopener noreffer"><code>Azure/static-web-apps-deploy@v1</code> action</a>
</li>
</ul>
<p>See the full workflow below.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Infra and App</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pull_request</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">opened, synchronize, reopened, closed]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">RESOURCE_GROUP</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;rg-swa-sso&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">RESOURCE_TAGS</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{&#34;owner&#34;:&#34;rick.roche&#34;, &#34;app&#34;:&#34;azure-swa-sso&#34;, &#34;repo&#34;:&#34;https://github.com/rick-roche/azure-static-web-apps-sso&#34; }&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">APP_NAME</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;swa-sso&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">LOCATION</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;westeurope&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">deploy-infra</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Checkout Repository</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-node@v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">node-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;16&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Azure Login</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">azure/login@v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">creds</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.AZURE_CREDENTIALS }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Infra</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">deploy_infra</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">github.event_name != &#39;pull_request&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">azure/CLI@v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">inlineScript</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            az deployment group create \
</span></span></span><span class="line"><span class="cl"><span class="sd">              --resource-group ${{ env.RESOURCE_GROUP }} \
</span></span></span><span class="line"><span class="cl"><span class="sd">              --template-file ./infra/main.bicep \
</span></span></span><span class="line"><span class="cl"><span class="sd">              --parameters \
</span></span></span><span class="line"><span class="cl"><span class="sd">                  appName=&#39;${{ env.APP_NAME }}&#39; \
</span></span></span><span class="line"><span class="cl"><span class="sd">                  location=&#39;${{ env.LOCATION }}&#39; \
</span></span></span><span class="line"><span class="cl"><span class="sd">                  repositoryUrl=&#39;https://github.com/rick-roche/azure-static-web-apps-sso&#39; \
</span></span></span><span class="line"><span class="cl"><span class="sd">                  repositoryToken=&#39;${{ secrets.WORKFLOW_TOKEN }}&#39; \
</span></span></span><span class="line"><span class="cl"><span class="sd">                  tags=&#39;${{ env.RESOURCE_TAGS }}&#39;</span><span class="w">            
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Get Static Web App API Key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">static_web_app_apikey</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">github.event_name != &#39;pull_request&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">azure/CLI@v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">inlineScript</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            APIKEY=$(az staticwebapp secrets list --name &#39;stapp-${{ env.APP_NAME }}&#39; | jq -r &#39;.properties.apiKey&#39;)
</span></span></span><span class="line"><span class="cl"><span class="sd">            echo &#34;::set-output name=APIKEY::$APIKEY&#34;</span><span class="w">            
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy WebApp to Static Web App</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">static_web_app_deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">github.event_name != &#39;pull_request&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">Azure/static-web-apps-deploy@v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">azure_static_web_apps_api_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.static_web_app_apikey.outputs.APIKEY }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">repo_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> </span><span class="c"># Used for GitHub integrations (i.e. PR comments)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;upload&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="c"># Build configuration for Azure Static Web Apps: https://aka.ms/swaworkflowconfig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">app_location</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;webapp&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">api_location</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">output_location</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;build&#34;</span><span class="w"> </span><span class="c"># relative to app_location</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="finalising-auth">Finalising auth</h2>
<p>At this stage you should have a GitHub workflow successfully deploying a Static Web App and a Key Vault to you resource group. You should also be able to browse to your web app using the generated URL (navigate into the Static Web App from the portal, and you will see your URL on the overview tab. e.g. <a href="https://gray-wave-03fb32a03.1.azurestaticapps.net" target="_blank" rel="noopener noreffer">https://gray-wave-03fb32a03.1.azurestaticapps.net</a>
).</p>
<figure class="image-center"><a class="lightgallery" href="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-azure-swa-overview.png" title="Static Web App Overview" data-thumbnail="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-azure-swa-overview.png" data-sub-html="<h2>Static Web App Overview</h2><p>Static Web App Overview</p>">
        
    </a><figcaption class="image-caption">Static Web App Overview</figcaption>
    </figure>
<div class="details admonition note open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-pencil-alt fa-fw" aria-hidden="true"></i>Note<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">Your app won&rsquo;t ask you to authenticate just yet!</div>
        </div>
    </div>
<p>To enable auth our next steps will be to</p>
<ul>
<li>create an AAD app registration</li>
<li>add it&rsquo;s client ID and secret as secrets in your key vault</li>
<li>configure the static web app to auto log you in if you aren&rsquo;t already or your token has expired</li>
</ul>
<h3 id="aad-app-registration">AAD App Registration</h3>
<p>An AAD app registration allows us to bind our application to a desired set of authentication flows and restrictions. Think of it as giving your application an identity inside AAD. <a href="https://www.re-mark-able.net/understanding-azure-active-directory-application-registrations/" target="_blank" rel="noopener noreffer">Mark Foppen wrote a lovely article</a>
 that may help demystify this a bit.</p>
<p>In this tutorial I&rsquo;ll just be using <a href="https://docs.microsoft.com/en-us/cli/azure/" target="_blank" rel="noopener noreffer">Azure CLI</a>
 to create one:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">az ad app create --display-name aadapp-swa-sso <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --available-to-other-tenants <span class="nb">false</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --identifier-uris api://stapp-swa-sso <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --reply-urls <span class="s1">&#39;https://gray-wave-03fb32a03.1.azurestaticapps.net/.auth/login/aad/callback&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --native-app <span class="nb">false</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="details admonition note open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-pencil-alt fa-fw" aria-hidden="true"></i>Note<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">Ensure you set the <code>--reply-urls</code> to be the generated URL of your app with the <code>/.auth/login/aad/callback</code> suffix. This allows AAD to call your app once the auth flow has completed.</div>
        </div>
    </div>
<p>Once created, we need to create an application secret for the application. For this tutorial you can do this via the portal following the <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret" target="_blank" rel="noopener noreffer">instructions here</a>
.</p>
<p>Copy the value of the secret as well as the <code>Application (client) ID</code> of the AAD app (found under the Overview section of the app).</p>
<h3 id="setup-key-vault-secrets">Setup Key Vault Secrets</h3>
<p>Once again, I&rsquo;ve used the portal for this tutorial. You can follow the <a href="https://docs.microsoft.com/en-us/azure/key-vault/secrets/quick-create-portal#add-a-secret-to-key-vault" target="_blank" rel="noopener noreffer">guide here</a>
 setting two secrets in your vault</p>
<ul>
<li><code>aadClientId</code> - this should be set to the Application (client) ID of your app registration</li>
<li><code>aadClientSecret</code> - this should be set to the value of the application secret created above</li>
</ul>
<h3 id="configuring-the-static-web-app">Configuring the Static Web App</h3>
<p>Configuration for Azure Static Web Apps is defined in the <a href="https://docs.microsoft.com/en-us/azure/static-web-apps/configuration" target="_blank" rel="noopener noreffer"><code>staticwebapp.config.json</code></a>
 file, which controls, among other things, authentication and authorisation.</p>
<p>I&rsquo;ve put mine in the <a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/webapp/staticwebapp.config.json" target="_blank" rel="noopener noreffer">root of the webapp folder</a>
 and set it to do the following to enable the auto-login SSO magic</p>
<ul>
<li>
<p>enable custom <code>auth</code> with <a href="https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-custom?tabs=aad#azure-active-directory-version-2" target="_blank" rel="noopener noreffer">Azure Active Directory Version 2</a>
 using the app settings references from earlier (<code>AAD_CLIENT_ID</code>, <code>AAD_CLIENT_SECRET</code>)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;auth&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;identityProviders&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;azureActiveDirectory&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;registration&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;openIdIssuer&#34;</span><span class="p">:</span> <span class="s2">&#34;https://login.microsoftonline.com/4af290c8-08df-440d-93db-cbac02bd9b19/v2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;clientIdSettingName&#34;</span><span class="p">:</span> <span class="s2">&#34;AAD_CLIENT_ID&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;clientSecretSettingName&#34;</span><span class="p">:</span> <span class="s2">&#34;AAD_CLIENT_SECRET&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="err">,</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>a <a href="https://docs.microsoft.com/en-us/azure/static-web-apps/configuration#fallback-routes" target="_blank" rel="noopener noreffer"><code>navigationFallback</code> route</a>
 to <code>index.html</code></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;navigationFallback&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;rewrite&#34;</span><span class="p">:</span> <span class="s2">&#34;index.html&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="err">,</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>specific rules for the apps <code>routes</code></p>
<ul>
<li>
<p>creates a <code>/login</code> route redirecting to AAD allowing anonymous access</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;route&#34;</span><span class="p">:</span> <span class="s2">&#34;/login&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;rewrite&#34;</span><span class="p">:</span> <span class="s2">&#34;/.auth/login/aad&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;allowedRoles&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;anonymous&#34;</span><span class="p">,</span> <span class="s2">&#34;authenticated&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="err">,</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><a href="https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-authorization?tabs=invitations#block-an-authentication-provider" target="_blank" rel="noopener noreffer">blocks all providers except AAD</a>
</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;route&#34;</span><span class="p">:</span> <span class="s2">&#34;/.auth/login/github&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">404</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;route&#34;</span><span class="p">:</span> <span class="s2">&#34;/.auth/login/twitter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">404</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="err">,</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>creates a <code>/logout</code> route redirecting to AAD allowing anonymous access</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;route&#34;</span><span class="p">:</span> <span class="s2">&#34;/logout&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;redirect&#34;</span><span class="p">:</span> <span class="s2">&#34;/.auth/logout&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;allowedRoles&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;anonymous&#34;</span><span class="p">,</span> <span class="s2">&#34;authenticated&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="err">,</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>enforces auth for all other routes (<code>/*</code>)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;route&#34;</span><span class="p">:</span> <span class="s2">&#34;/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;allowedRoles&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;authenticated&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
</li>
<li>
<p>sets up a response override that if an unauthenticated user hits a page, they should be redirected to the <code>/login</code> route</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;responseOverrides&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;401&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;redirect&#34;</span><span class="p">:</span> <span class="s2">&#34;/login&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;statusCode&#34;</span><span class="p">:</span> <span class="mi">302</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<p>The combination of all of the above means that anyone accessing the site that isn&rsquo;t logged in will be routed to the <code>/login</code> route which will ensure that the AAD auth flow is completed!</p>
<h2 id="finishing-up">Finishing up!</h2>
<p>Commit all your outstanding changes, push to GitHub and watch your action deploy&hellip; once done, access your web app in the browser, and it should redirect you to login</p>
<a class="lightgallery" href="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-login.png" title="Azure AD Login" data-thumbnail="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-login.png">
        
    </a>
<p>Initially you will need to provide admin consent for your AD app registration to read the logged-in user details</p>
<a class="lightgallery" href="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-consent.png" title="Azure AD User Consent" data-thumbnail="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-consent.png">
        
    </a>
<p>If all has gone well you should see the default page after login completes</p>
<a class="lightgallery" href="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-welcome.png" title="Welcome Page" data-thumbnail="/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-welcome.png">
        
    </a>
<p>And that&rsquo;s it! Hope you enjoyed this tutorial and that it helps you setup SSO for your Static Web Apps!</p>
<div class="details admonition info open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-info-circle fa-fw" aria-hidden="true"></i>Info<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">A reminder that all code is available on GitHub <a href="https://github.com/rick-roche/azure-static-web-apps-sso" target="_blank" rel="noopener noreffer">over here</a></div>
        </div>
    </div>
<p>Featured image background by <a href="https://unsplash.com/@helloimnik?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" title="Hello I&#39;m Nik" target="_blank" rel="noopener noreffer">Hello I&rsquo;m Nik</a>
 on <a href="https://unsplash.com/s/photos/sign-in?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreffer">Unsplash</a>
</p>
]]></description></item><item><title>Bicep Modules: Refactor, Compose, Reuse</title><link>https://www.rickroche.com/2021/07/bicep-modules-refactor-compose-reuse/</link><pubDate>Sun, 11 Jul 2021 13:00:00 +0200</pubDate><author>
Richard Roché</author><guid>https://www.rickroche.com/2021/07/bicep-modules-refactor-compose-reuse/</guid><description><![CDATA[<div class="featured-image">
                <img src="https://www.rickroche.com/2021/07/bicep-modules-refactor-compose-reuse/bicep-modules-cover.png" referrerpolicy="no-referrer">
            </div><p>In my previous post I touched on the things I learnt while <a href="https://www.rickroche.com/2021/06/migrating-azure-arm-templates-to-bicep/" title="Migrating Azure ARM Templates to Bicep" target="_blank" rel="noopener noreffer">migrating ARM templates to Bicep</a>
. <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview" title="What is Bicep?" target="_blank" rel="noopener noreffer">Bicep</a>
 also introduces the concept of modules to enable template reuse. I took some time to refactor a composite application that had already been converted from using ARM to Bicep templates, to use Bicep modules. This post will cover the things that I learnt by working through that process.</p>
<h2 id="why-modules">Why modules?</h2>
<p>From the <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/modules" target="_blank" rel="noopener noreffer">Bicep documentation</a>
:</p>
<blockquote>
<p>Bicep enables you to break down a complex solution into modules. A Bicep module is a set of one or more resources to be deployed together. Modules abstract away complex details of the raw resource declaration, which can increase readability. You can reuse these modules, and share them with other people. Bicep modules are transpiled into a single ARM template with <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates#nested-template" title="Nested Templates" target="_blank" rel="noopener noreffer">nested templates</a>
 for deployment.</p>
</blockquote>
<p>One of the traps we fell into with ARM templates was duplicating templates to make composing and deploying the resources we need easier. Any opportunity to make the deployment of infrastructure more readable, more reusable, and more composable are excellent reasons for me to give it a go. My goal when doing this refactor was to</p>
<ul>
<li>get rid of any duplication by creating fine-grained modules, designed to be reused</li>
<li>ensure that all main templates are super easy to use by composing modules together in a way that makes sense for the application</li>
<li>enable reusability of the fine-grained modules in other projects going forward.</li>
</ul>
<p>In short: let&rsquo;s make the infra readable, composable and reusable.</p>
<h2 id="notable-learnings">Notable learnings</h2>
<h3 id="reusable-modules">Reusable Modules</h3>
<p>I went with the approach of trying to make a reusable module for each Azure resource type and putting the modules into a folder called <code>modules</code> and making sub folders for template groupings. For example,</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">modules/
</span></span><span class="line"><span class="cl">    appInsights.bicep
</span></span><span class="line"><span class="cl">    appServicePlan.bicep
</span></span><span class="line"><span class="cl">    functionApp.bicep
</span></span><span class="line"><span class="cl">    logAnalytics.bicep
</span></span><span class="line"><span class="cl">    storageAccount/
</span></span><span class="line"><span class="cl">        storageAccount.bicep
</span></span><span class="line"><span class="cl">        tables.bicep
</span></span></code></pre></td></tr></table>
</div>
</div><p>Personally I prefer to have one module to create a storage account and another to add tables to that storage account etc. This level of granularity felt like a good place to start.</p>
<h3 id="referencing-existing-resources-inside-a-module">Referencing existing resources inside a module</h3>
<p>By making fine-grained modules, there were a number of use cases where I would need to reference an existing resource. For example, creating tables in a storage account requires an existing storage account. <a href="https://github.com/Azure/bicep/blob/main/docs/spec/resources.md#referencing-existing-resources" title="Bicep Existing Resources" target="_blank" rel="noopener noreffer">Referencing an existing resource</a>
 is really easy &ndash; you only need to know its name and can reference it as follows,</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="c1">// Lookup an existing resource</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">resource</span><span class="w"> </span><span class="nv">storageAccount</span><span class="w"> </span><span class="s">&#39;Microsoft.Storage/storageAccounts@2021-02-01&#39;</span><span class="w"> </span><span class="kd">existing</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="nv">storageAccountName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">// Optional if the existing resource is in a different resource group or subscription</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">scope</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">(</span><span class="nv">subscriptionId</span><span class="p">,</span><span class="w"> </span><span class="nv">resourceGroupName</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// You can now use the resource as if you had created it. e.g.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nv">outputs</span><span class="w"> </span><span class="nv">storageAccountResourceId</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nv">storageAccount</span><span class="p">.</span><span class="nv">id</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="loops">Loops</h3>
<p>I really like the <a href="https://github.com/Azure/bicep/blob/main/docs/spec/loops.md" title="Bicep loops" target="_blank" rel="noopener noreffer">loops</a>
 feature. This allows you to iterate over an array setting multiple properties or creating multiple resources etc. This came in super handy for a storage account tables module that can create multiple tables in one go. E.g.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">storageAccountName</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">param</span><span class="w"> </span><span class="nv">tables</span><span class="w"> </span><span class="nv">array</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">container</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;default&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;replace&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">resource</span><span class="w"> </span><span class="nv">storageAccount</span><span class="w"> </span><span class="s">&#39;Microsoft.Storage/storageAccounts@2021-02-01&#39;</span><span class="w"> </span><span class="kd">existing</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="nv">storageAccountName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">resource</span><span class="w"> </span><span class="nv">storageAccountTables</span><span class="w"> </span><span class="s">&#39;Microsoft.Storage/storageAccounts/tableServices/tables@2021-02-01&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="kd">for</span><span class="w"> </span><span class="nv">table</span><span class="w"> </span><span class="kd">in</span><span class="w"> </span><span class="nv">tables</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">storageAccount</span><span class="p">.</span><span class="nv">name</span><span class="si">}</span><span class="s">/</span><span class="si">${</span><span class="nv">table</span><span class="p">.</span><span class="nv">container</span><span class="si">}</span><span class="s">/</span><span class="si">${</span><span class="nv">table</span><span class="p">.</span><span class="nv">name</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">dependsOn</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">storageAccount</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">output</span><span class="w"> </span><span class="nv">storageAccountTableNames</span><span class="w"> </span><span class="nv">array</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="kd">for</span><span class="w"> </span><span class="p">(</span><span class="nv">table</span><span class="p">,</span><span class="w"> </span><span class="nv">i</span><span class="p">)</span><span class="w"> </span><span class="kd">in</span><span class="w"> </span><span class="nv">tables</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="nv">storageAccountTables</span><span class="p">[</span><span class="nv">i</span><span class="p">].</span><span class="nv">name</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}]</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Note the use of the different styles of the loops in the <code>storageAccountTables</code> resource and the outputs.</p>
<h3 id="functions-and-expressions">Functions and Expressions</h3>
<p>There are loads of <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions" title="Bicep Functions" target="_blank" rel="noopener noreffer">functions</a>
 and <a href="https://github.com/Azure/bicep/blob/main/docs/spec/expressions.md" title="Bicep expressions" target="_blank" rel="noopener noreffer">expressions</a>
 that you can use in your Bicep files and I won&rsquo;t go into all of them. There were a few that I used regularly, and it&rsquo;s hopefully useful that I call them out.</p>
<h4 id="union">Union</h4>
<blockquote>
<p><code>union(arg1, arg2, arg3, ...)</code>
Returns a single array or object with all elements from the parameters. Duplicate values or keys are only included once.</p>
</blockquote>
<p>I used union everywhere I wanted to have fixed (opinionated) defaults in the module, but allow additional parameters to be merged in. For example, with function apps I defined base settings I want all function apps to have and still allow for additional app settings to be passed in and merged with the base.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="p">@</span><span class="nf">description</span><span class="p">(</span><span class="s">&#39;Additional app settings for your function app&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">param</span><span class="w"> </span><span class="nv">additionalAppSettings</span><span class="w"> </span><span class="nv">object</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">var</span><span class="w"> </span><span class="nv">appSettingsBase</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">FUNCTIONS_EXTENSION_VERSION</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;~3&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">FUNCTIONS_WORKER_RUNTIME</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;dotnet&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">WEBSITE_RUN_FROM_PACKAGE</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;1&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">AzureWebJobsStorage</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;DefaultEndpointsProtocol=https;AccountName=</span><span class="si">${</span><span class="nv">storageAccount</span><span class="p">.</span><span class="nv">name</span><span class="si">}</span><span class="s">;EndpointSuffix=</span><span class="si">${</span><span class="nf">environment</span><span class="p">().</span><span class="nv">suffixes</span><span class="p">.</span><span class="nv">storage</span><span class="si">}</span><span class="s">;AccountKey=</span><span class="si">${</span><span class="nf">listKeys</span><span class="p">(</span><span class="nv">storageAccount</span><span class="p">.</span><span class="nv">id</span><span class="p">,</span><span class="w"> </span><span class="nv">storageAccount</span><span class="p">.</span><span class="nv">apiVersion</span><span class="p">).</span><span class="nv">keys</span><span class="p">[</span><span class="nv">0</span><span class="p">].</span><span class="nv">value</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">var</span><span class="w"> </span><span class="nv">appSettings</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">union</span><span class="p">(</span><span class="nv">appSettingsBase</span><span class="p">,</span><span class="w"> </span><span class="nv">additionalAppSettings</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>We can also get rid of all the hardcoded schema API versions when looking up keys against a resource by using <code>&lt;resource&gt;.apiVersion</code> as well as hardcoded suffixes by using the <a href="https://www.rickroche.com/2021/06/migrating-azure-arm-templates-to-bicep/#environment-urls" title="Environment URLs" target="_blank" rel="noopener noreffer">environment function</a>
, in this case using the storage suffix: <code>environment().suffixes.storage</code>.</p>
<p>Another super useful call out would be to the <code>listKeys</code> function. It allows you to get connection strings or keys from your resources and is super handy (see the <code>AzureWebJobsStorage</code> example above). <a href="https://blog.johnnyreilly.com/about" title="About John Reilly" target="_blank" rel="noopener noreffer">John Reilly</a>
 went into <a href="https://blog.johnnyreilly.com/2021/07/07/output-connection-strings-and-keys-from-azure-bicep" title="John Reilly - Output connection strings and keys from Azure Bicep" target="_blank" rel="noopener noreffer">detail on this over here</a>
, please have a read!</p>
<h4 id="ternary">Ternary</h4>
<p>I found that with Bicep I use the <a href="https://github.com/Azure/bicep/blob/main/docs/spec/expressions.md#ternary-operator" title="Bicep Ternary Operator" target="_blank" rel="noopener noreffer">ternary operator</a>
 a lot in my main templates when composing the modules. For example, only enabling a secondary region when the environment is <code>nonprod</code> or <code>prod</code> but not in <code>dev</code> can easily be described as,</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nv">secondaryRegionEnabled</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">(</span><span class="nf">contains</span><span class="p">(</span><span class="nv">env</span><span class="p">,</span><span class="w"> </span><span class="s">&#39;prod&#39;</span><span class="p">))</span><span class="w"> </span><span class="p">?</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Much cleaner and readable without all the JSON around it.</p>
<h2 id="composing-modules-together">Composing modules together</h2>
<p>Every Bicep file can be consumed as a module which is an awesome feature. I chose to break my files up as follows:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">infra/
</span></span><span class="line"><span class="cl">  myApp/
</span></span><span class="line"><span class="cl">    main.bicep - loads up the app.bicep and monitoring.bicep modules
</span></span><span class="line"><span class="cl">    app.bicep - uses the fine grained modules - app service plans, functions, azure storage etc
</span></span><span class="line"><span class="cl">    monitoring.bicep - uses the fine grained modules - app insights, log analytics etc
</span></span><span class="line"><span class="cl">shared-infra/
</span></span><span class="line"><span class="cl">  modules/
</span></span><span class="line"><span class="cl">    &lt;all the fine-grained modules&gt;
</span></span></code></pre></td></tr></table>
</div>
</div><p>In my <code>main.bicep</code> I reference the two local Bicep files as modules</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">monitoring</span><span class="w"> </span><span class="s">&#39;monitoring.bicep&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">appName</span><span class="si">}</span><span class="s">-monitoring&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">tags</span><span class="p">:</span><span class="w"> </span><span class="nv">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">appInsightsName</span><span class="p">:</span><span class="w"> </span><span class="nv">appInsightsName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">logAnalyticsWsName</span><span class="p">:</span><span class="w"> </span><span class="nv">logAnalyticsWsName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">module</span><span class="w"> </span><span class="nv">app</span><span class="w"> </span><span class="s">&#39;app.bicep&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">appName</span><span class="si">}</span><span class="s">-app&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">tags</span><span class="p">:</span><span class="w"> </span><span class="nv">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">appName</span><span class="p">:</span><span class="w"> </span><span class="nv">appName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">storageAccountName</span><span class="p">:</span><span class="w"> </span><span class="nv">storageAccountName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">appInsightsName</span><span class="p">:</span><span class="w"> </span><span class="nv">monitoring</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">appInsightsName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">logAnalyticsWsName</span><span class="p">:</span><span class="w"> </span><span class="nv">monitoring</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">logAnalyticsWsName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">monitoringResourceGroup</span><span class="p">:</span><span class="w"> </span><span class="nv">monitoring</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">ResourceGroupName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Note that the <code>app</code> module relies on the outputs of the monitoring module &ndash; this helps Bicep figure out the dependencies so that you don&rsquo;t need to define <code>dependsOn</code> any more.</p>
<p>Then in <code>monitoring.bicep</code> I can reference fine-grained reusable modules that I&rsquo;d like to share with other projects in much the same way</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">log</span><span class="w"> </span><span class="s">&#39;../../shared-infra/modules/logAnalytics.bicep&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">appName</span><span class="si">}</span><span class="s">-log&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">tags</span><span class="p">:</span><span class="w"> </span><span class="nv">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">logAnalyticsWsName</span><span class="p">:</span><span class="w"> </span><span class="nv">logAnalyticsWsName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">module</span><span class="w"> </span><span class="nv">appi</span><span class="w"> </span><span class="s">&#39;../../shared-infra/modules/appInsights.bicep&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">appName</span><span class="si">}</span><span class="s">-appi&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">tags</span><span class="p">:</span><span class="w"> </span><span class="nv">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">appInsightsName</span><span class="p">:</span><span class="w"> </span><span class="nv">appInsightsName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">logAnalyticsWsName</span><span class="p">:</span><span class="w"> </span><span class="nv">log</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">logAnalyticsWsName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Another thing to note is the <code>name</code> of your module is what is shown in the <code>Deployments</code> tab of the Azure Portal, so make these make sense to you for easy debugging if deployments are breaking.</p>
<h2 id="sharing-modules">Sharing modules</h2>
<p>At this stage I&rsquo;ve got different two styles of modules &ndash; app specific modules breaking up my <code>main.bicep</code>, making it easier to read and maintain and fine-grained templates in a <code>modules</code> folder that I can use across all the apps in my <code>infra</code> folder. Readable &ndash; check! Composable &ndash; check! Reusable &ndash; only inside this repo, so half a check!</p>
<p>The current version of Bicep (<code>v0.4.63</code>), does not have a native mechanism to externally share modules across projects. The good news is that this is being looked at and will <em>hopefully</em> be in the v0.5 release. The issues to watch are:</p>
<ul>
<li><a href="https://github.com/Azure/bicep/issues/2128" title="[Story] Bicep Registry" target="_blank" rel="noopener noreffer">[Story] Bicep Registry</a>
</li>
<li><a href="https://github.com/Azure/bicep/issues/660" title="Ability to reference &#39;external&#39; modules" target="_blank" rel="noopener noreffer">Ability to reference &ldquo;external&rdquo; modules</a>
</li>
<li><a href="https://github.com/Azure/bicep/issues/1242" title="Modules need versions" target="_blank" rel="noopener noreffer">Modules need versions</a>
</li>
</ul>
<p>In the interim, I am using <a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules" title="Git Submodules" target="_blank" rel="noopener noreffer">Git submodules</a>
 to solve this, although this will not work with all CI/CD tooling when using private repos. To enable this, I moved the modules in <code>shared-infra</code> into its own repo and added it back to my project.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git submodule add &lt;path-to-repo&gt; shared-infra/
</span></span><span class="line"><span class="cl">git submodule update --init
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="testing-locally">Testing locally</h2>
<p>To quickly validate the individual modules and main templates on my local machine, I wrote a <a href="https://gist.github.com/rick-roche/57c90abae06630060afce1e76372b13b" title="infra.sh Gist" target="_blank" rel="noopener noreffer">simple bash script</a>
 to either</p>
<ul>
<li>build the Bicep file, outputting to the terminal rather than writing to file or</li>
<li>validate the Bicep template against a resource group</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="nv">RG</span><span class="o">=</span>replace-with-your-rg
</span></span><span class="line"><span class="cl"><span class="nv">SUB</span><span class="o">=</span>replace-with-your-sub
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">op</span><span class="o">=</span><span class="nv">$1</span> <span class="c1"># lint, validate, create</span>
</span></span><span class="line"><span class="cl"><span class="nv">main_shared</span><span class="o">=</span><span class="nv">$2</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$main_shared</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s1">&#39;main&#39;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> <span class="nv">path</span><span class="o">=</span><span class="s1">&#39;./infra&#39;</span> <span class="o">&amp;&amp;</span> <span class="nv">filename</span><span class="o">=</span><span class="s1">&#39;main.bicep&#39;</span><span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$main_shared</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s1">&#39;shared&#39;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> <span class="nv">path</span><span class="o">=</span><span class="s1">&#39;./shared-infra&#39;</span> <span class="o">&amp;&amp;</span> <span class="nv">filename</span><span class="o">=</span><span class="s1">&#39;*.bicep&#39;</span><span class="p">;</span> <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">lint <span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    az bicep build --file <span class="s2">&#34;</span><span class="nv">$f</span><span class="s2">&#34;</span> --stdout
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">validate <span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    az deployment group validate --resource-group <span class="s2">&#34;</span><span class="nv">$RG</span><span class="s2">&#34;</span> --subscription <span class="s2">&#34;</span><span class="nv">$SUB</span><span class="s2">&#34;</span> -f <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span> -p <span class="nv">env</span><span class="o">=</span>dev
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> f in <span class="k">$(</span>find <span class="s2">&#34;</span><span class="nv">$path</span><span class="s2">&#34;</span> -name <span class="s2">&#34;</span><span class="nv">$filename</span><span class="s2">&#34;</span><span class="k">)</span> <span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$f</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$op</span> <span class="s2">&#34;</span><span class="nv">$f</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The script allows one to test either <code>main</code> or <code>shared</code> templates, linting them or validating them:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./infra.sh lint main
</span></span><span class="line"><span class="cl">./infra.sh lint shared
</span></span><span class="line"><span class="cl">./infra.sh validate main
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="finishing-up">Finishing up</h2>
<p>After the refactor, my project is in a far cleaner state and the infrastructure is much easier to follow. A second plus is that we now have a separate repo of our shared modules that can become a shared asset across our various teams (using Git submodules for now and the Bicep registry in the future).</p>
<p>Featured image background by <a href="https://unsplash.com/@lazycreekimages?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" title="Michael Dziedzic" target="_blank" rel="noopener noreffer">Michael Dziedzic</a>
 on <a href="https://unsplash.com/s/photos/modules?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreffer">Unsplash</a>
</p>
]]></description></item><item><title>Migrating Azure ARM templates to Bicep</title><link>https://www.rickroche.com/2021/06/migrating-azure-arm-templates-to-bicep/</link><pubDate>Fri, 18 Jun 2021 09:00:00 +0200</pubDate><author>
Richard Roché</author><guid>https://www.rickroche.com/2021/06/migrating-azure-arm-templates-to-bicep/</guid><description><![CDATA[<div class="featured-image">
                <img src="https://www.rickroche.com/2021/06/migrating-azure-arm-templates-to-bicep/migrating-to-bicep-cover.png" referrerpolicy="no-referrer">
            </div><p>You may have heard of <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview" title="What is Bicep?" target="_blank" rel="noopener noreffer">Bicep</a>
, and you may be wondering how much effort it is going to take to move all your ARM templates to this new way of deploying Azure resources.</p>
<p>I gave migrating from ARM to Bicep a go. This post will cover going from JSON ARM templates to shiny new Bicep templates that have no errors and don&rsquo;t contain any warnings or linting issues!</p>
<h2 id="why-should-you-migrate">Why should you migrate?</h2>
<p>If you have ever deployed infra to Azure you have most likely used ARM templates before. They do the job, the docs aren&rsquo;t bad and there are built in tasks for ADO which make deploying them super straightforward. However, working with JSON is always going to be verbose, ARM templates have a lot of boilerplate required, making reusable templates is clunky and deployments can get tricky.</p>
<p>Aiming to address these (and other) issues, Azure is working on a project called Bicep. From the <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview" title="What is Bicep?" target="_blank" rel="noopener noreffer">projects overview</a>
:</p>
<blockquote>
<p>Bicep is a domain-specific language (DSL) that uses declarative syntax to deploy Azure resources. It provides concise syntax, reliable type safety, and support for code reuse. We believe Bicep offers the best authoring experience for your Azure infrastructure as code solutions.</p>
</blockquote>
<p>Essentially it is a DSL built on-top of ARM that makes the development of templates simpler and promotes reuse through modules. Judging by the release rate <a href="https://github.com/Azure/bicep/releases" title="Bicep Releases" target="_blank" rel="noopener noreffer">on their GitHub</a>
 the team is working hard to make it awesome very quickly and according to them, as of v0.3, Bicep is now supported by Microsoft Support Plans and Bicep has 100% parity with what can be accomplished with ARM Templates. So Bicep is definitely prod ready (at time of writing v0.4.63 was the latest release)!</p>
<h2 id="getting-started">Getting started</h2>
<p>Ensure you have the <a href="https://github.com/Azure/bicep/releases" title="Bicep Releases" target="_blank" rel="noopener noreffer">latest version</a>
 of Bicep installed (excellent installation guide <a href="https://github.com/Azure/bicep/blob/main/docs/installing.md" title="Setup your Bicep development environment" target="_blank" rel="noopener noreffer">here</a>
). I went with the <code>az cli</code> install option and all my examples will be using that variant. I also use Visual Studio Code and installed the <a href="https://github.com/Azure/bicep/blob/main/docs/installing.md#install-the-bicep-vs-code-extension" title="Install the Bicep VS Code extension" target="_blank" rel="noopener noreffer">Bicep VS Code Extension</a>
 for enhanced editing.</p>
<h2 id="decompiling-arm-to-bicep">Decompiling ARM to Bicep</h2>
<p>First things first, it is possible to <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/decompile?tabs=azure-cli" title="Decompiling ARM template JSON to Bicep" target="_blank" rel="noopener noreffer">decompile</a>
 an ARM template into Bicep by running the below command (all our ARM templates are in separate folders with an <code>azuredeploy.json</code> file for the template)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">az bicep decompile --file azuredeploy.json
</span></span></code></pre></td></tr></table>
</div>
</div><p>This creates a <code>azuredeploy.bicep</code> file in the same directory. Depending on your template, you will most likely get a stream of yellow warnings in the console, or potentially some red errors: don&rsquo;t panic! Even a single error will result in all the console output being red (at least on macOS) and at the very least you will get this warning message:</p>
<blockquote>
<p>WARNING: Decompilation is a best-effort process, as there is no guaranteed mapping from ARM JSON to Bicep.
You may need to fix warnings and errors in the generated bicep file(s), or decompilation may fail entirely if an accurate conversion is not possible.
If you would like to report any issues or inaccurate conversions, please see <a href="https://github.com/Azure/bicep/issues" target="_blank" rel="noopener noreffer">https://github.com/Azure/bicep/issues</a>
.</p>
</blockquote>
<p><strong>It is important to note that the migration from ARM to Bicep will highlight issues in your ARM templates. ARM was more forgiving in certain aspects, after the migration, your templates will be in a better state.</strong></p>
<p>Errors come in the form of <code>Error BCPXXX: Description</code> and the descriptions generally let you get to the root of the problem quickly. E.g. <em>Error BCP037: The property &ldquo;location&rdquo; is not allowed on objects of type &ldquo;Microsoft.EventHub/namespaces/eventhubs&rdquo;. Permissible properties include &ldquo;dependsOn&rdquo;.</em></p>
<p>If the decompilation gives you any errors, my preferred approach is to fix the ARM template and then run the decompilation again until you get a &ldquo;clean&rdquo; decompilation (warnings are fine, just ensure you decompile with no errors).</p>
<p>Warnings come in two flavours, ones that have a code (<em>Warning BCPXXX</em>) and ones that don&rsquo;t have a code but have a link at the end. As of <code>v0.4</code> there is a <a href="https://github.com/Azure/bicep/blob/main/docs/linter.md" title="Bicep Linter" target="_blank" rel="noopener noreffer">linter</a>
 which helps you get your templates into great shape &ndash; these are the warnings with the links at the end. Both kinds of warnings are generally quick to fix and are sensible updates to start getting the benefits from Bicep.</p>
<h2 id="common-errors-and-how-to-fix-them">Common errors and how to fix them</h2>
<h3 id="user-defined-functions-are-not-supported-bcp007-bcp057">User defined functions are not supported (BCP007, BCP057)</h3>
<p>If you have created user defined functions in ARM, these will not be decompiled (follow the <a href="https://github.com/Azure/bicep/issues/2" target="_blank" rel="noopener noreffer">issue</a>
) and you will get two cryptic errors</p>
<blockquote>
<p>Error BCP007: This declaration type is not recognized. Specify a parameter, variable, resource, or output declaration.
Error BCP057: The name &ldquo;<code>functionName</code>&rdquo; does not exist in the current context.</p>
</blockquote>
<p><strong>To fix, move the function logic into your template (this may require duplication, but can be neatened up in the Bicep template later)</strong></p>
<h3 id="nested-dependson-bcp034">Nested dependsOn (BCP034)</h3>
<p>Sometimes the use of nested <code>dependsOn</code> properties in your ARM templates gets weird with Bicep (<code>dependsOn</code> can often be <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/compare-template-syntax#resource-dependencies" title="Bicep Resource dependencies" target="_blank" rel="noopener noreffer">removed entirely</a>
 in Bicep). The ARM version would have validated and deployed perfectly fine however you will get the following error when decompiling to Bicep.</p>
<blockquote>
<p>Error BCP034: The enclosing array expected an item of type &ldquo;module[] | (resource | module) | resource[]&rdquo;, but the provided item was of type &ldquo;string&rdquo;.</p>
</blockquote>
<p>Fortunately <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/compare-template-syntax#resource-dependencies" title="Bicep Resource dependencies" target="_blank" rel="noopener noreffer">Bicep handles dependencies</a>
 in a much smarter way than ARM meaning you can safely remove your nested dependencies and run again.</p>
<blockquote>
<p>For Bicep, you can set an explicit dependency but this approach isn&rsquo;t recommended. Instead, rely on implicit dependencies. An implicit dependency is created when one resource declaration references the identifier of another resource.</p>
</blockquote>
<p><strong>To fix, delete your nested dependencies and validate (consider removing your <code>dependsOn</code> references entirely)</strong></p>
<h3 id="strict-schema-validation-bcp037-bcp073">Strict schema validation (BCP037, BCP073)</h3>
<p>Bicep validates the schema of each resource much more diligently than ARM. As a result you will probably find a bunch of places where you have added properties like <code>location</code> or <code>tags</code> to a resource that doesn&rsquo;t support them and ARM didn&rsquo;t mind this. These will be expressed as <code>Error BCP037</code>.</p>
<p>E.g. I had <code>location</code> on <a href="https://docs.microsoft.com/en-us/azure/templates/microsoft.eventhub/namespaces/eventhubs?tabs=json" title="Microsoft.EventHub/namespaces/eventhubs schema" target="_blank" rel="noopener noreffer">Microsoft.EventHub/namespaces/eventhubs</a>
</p>
<blockquote>
<p>Error BCP037: The property &ldquo;location&rdquo; is not allowed on objects of type &ldquo;Microsoft.EventHub/namespaces/eventhubs&rdquo;. Permissible properties include &ldquo;dependsOn&rdquo;.</p>
</blockquote>
<p>The one error I did run into was for <a href="https://azure.microsoft.com/en-us/services/logic-apps/" title="Azure Logic Apps" target="_blank" rel="noopener noreffer">Azure Logic Apps</a>
 where the schema for <a href="https://docs.microsoft.com/en-us/azure/templates/microsoft.logic/workflows?tabs=json" title="Microsoft.Logic/workflows schema" target="_blank" rel="noopener noreffer">Microsoft.Logic/workflows</a>
 is missing the <code>identity</code> property needed for using Managed Identity with your Logic App:</p>
<blockquote>
<p>Error BCP037: The property &ldquo;identity&rdquo; is not allowed on objects of type &ldquo;Microsoft.Logic/workflows&rdquo;. Permissible properties include &ldquo;dependsOn&rdquo;.</p>
</blockquote>
<p>Another common error I encountered was having read-only parameters in my templates (these tend to come along if you have exported templates from the Azure Portal). E.g.</p>
<blockquote>
<p>Error BCP073: The property &ldquo;kind&rdquo; is read-only. Expressions cannot be assigned to read-only properties.</p>
</blockquote>
<p><strong>To fix both types of errors, remove the properties that the error messages highlight and run the decompilation again.</strong></p>
<h3 id="reserved-words-as-parameters-bcp079">Reserved words as parameters (BCP079)</h3>
<p>In ARM templates you can have the same name for a parameter, variable, function etc as these are all addressed directly using <code>parameters('name')</code> or <code>variables('name')</code> etc. In Bicep the syntax is simplified and as such you will get errors if you have used a reserved word as a parameter. We had a couple of templates taking in a parameter called <code>description</code> resulting in a cryptic error message:</p>
<blockquote>
<p>Error BCP079: This expression is referencing its own declaration, which is not allowed.</p>
</blockquote>
<p><strong>To fix, rename the parameter in your ARM template, update its usages and run the decompilation again.</strong></p>
<h2 id="common-warnings-and-how-to-fix-them">Common warnings and how to fix them</h2>
<p>Hopefully by now you are out of the error zone, for warnings you can now start editing the Bicep file itself. The <a href="https://github.com/Azure/bicep/blob/main/docs/installing.md#install-the-bicep-vs-code-extension" title="Install the Bicep VS Code extension" target="_blank" rel="noopener noreffer">Bicep VS Code Extension</a>
 gives you great intellisense and highlights issues in the Bicep file.</p>
<p>To test an update on your Bicep file, run</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">az bicep build --file azuredeploy.bicep
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="schema-warnings-enums-and-types-bcp035-bcp036-bcp037-bcp073-bcp081-bcp174">Schema warnings, enums and types (BCP035, BCP036, BCP037, BCP073, BCP081, BCP174)</h3>
<p><a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/data-types" title="Bicep Data Types" target="_blank" rel="noopener noreffer">Data types</a>
 in Bicep are stricter than in ARM. If you have used <code>True</code> or <code>False</code> for booleans these need to be updated to be <code>true</code> and <code>false</code>. When using enums, they need to match the enums in the schema exactly (case-sensitive). E.g. if you used <code>ascending</code> and the template schema defines <code>Ascending</code> this adjustment needs to be made. Examples of these warnings are shown below.</p>
<blockquote>
<p>Warning BCP036: The property &ldquo;kafkaEnabled&rdquo; expected a value of type &ldquo;bool | null&rdquo; but the provided value is of type &ldquo;&lsquo;False&rsquo; | &lsquo;True&rsquo;&rdquo;.
Warning BCP036: The property &ldquo;order&rdquo; expected a value of type &ldquo;&lsquo;Ascending&rsquo; | &lsquo;Descending&rsquo; | null&rdquo; but the provided value is of type &ldquo;&lsquo;ascending&rsquo;&rdquo;.</p>
</blockquote>
<p>Similar to the errors thrown by BCP037 and BCP073 you will get warnings on schema mismatches using codes BCP035, BCP037 and BCP073. These highlight schema mismatches, missing properties and read-only properties used. Examples below.</p>
<blockquote>
<p>Warning BCP035: The specified &ldquo;object&rdquo; declaration is missing the following required properties: &ldquo;options&rdquo;.
Warning BCP037: The property &ldquo;apiVersion&rdquo; is not allowed on objects of type &ldquo;Output&rdquo;. No other properties are allowed.
Warning BCP073: The property &ldquo;status&rdquo; is read-only. Expressions cannot be assigned to read-only properties.</p>
</blockquote>
<p>I did find an instance where the Bicep template matches the schema perfectly however warnings are still thrown. I think this is due to <a href="https://github.com/Azure/bicep/issues/3215" title="Property values originating from resource properties are not validated correctly" target="_blank" rel="noopener noreffer">this issue</a>
 and fortunately doesn&rsquo;t break anything.</p>
<p><strong>To fix, update the offending property to match the schema or data type</strong></p>
<h3 id="string-interpolation">String interpolation</h3>
<p><code>concat</code> gets to disappear in Bicep thanks to the lovely string interpolation option. I experienced two variants:</p>
<blockquote>
<p>Warning prefer-interpolation: Use string interpolation instead of the concat function. [https://aka.ms/bicep/linter/prefer-interpolation]</p>
</blockquote>
<p><strong>To fix, search for all usages of <code>concat</code> and replace with the new syntax: <code>'string-${var}'</code>.</strong></p>
<blockquote>
<p>Warning simplify-interpolation: Remove unnecessary string interpolation. [https://aka.ms/bicep/linter/simplify-interpolation]</p>
</blockquote>
<p><strong>To fix, remove the <code>${}</code> and reference the variable directly. E.g. <code>'${variable}'</code> becomes <code>variable</code>.</strong></p>
<h3 id="environment-urls">Environment URLs</h3>
<p>We had a lot of hardcoded URL&rsquo;s in our templates for storage suffixes, front door etc. There is a much better way to do this by using the <code>environment()</code> function. Caveat here, if you had <code>environment</code> as a parameter to your ARM template, update this to be something else like <code>env</code> for example otherwise you won&rsquo;t be able to use the function.</p>
<blockquote>
<p>Warning no-hardcoded-env-urls: Environment URLs should not be hardcoded. Use the environment() function to ensure compatibility across clouds. Found this disallowed host: &ldquo;core.windows.net&rdquo; [https://aka.ms/bicep/linter/no-hardcoded-env-urls]</p>
</blockquote>
<p><strong>To fix, have a look at all the <a href="https://docs.microsoft.com/en-za/azure/azure-resource-manager/templates/template-functions-deployment?tabs=json#environment" title="Environment Template Function" target="_blank" rel="noopener noreffer">URLs that the environment function provides</a>
 and replace your hard-coded version with <code>environment().&lt;property&gt;</code>.</strong></p>
<p>E.g. for Azure Storage where <code>core.windows.net</code> had been hard coded, replacing with <code>environment().suffixes.storage</code> gives the desired result.</p>
<h3 id="scopes-bcp174">Scopes (BCP174)</h3>
<p>Bicep introduces the concept of target scopes which dictates the scope that resources within that deployment are created in. This gives you a new way to define resources where you previously would have used the <code>/providers/</code> syntax (role assignments, diagnostic settings etc). The warning you get looks as follows.</p>
<blockquote>
<p>Warning BCP174: Type validation is not available for resource types declared containing a &ldquo;/providers/&rdquo; segment. Please instead use the &ldquo;scope&rdquo; property. [https://aka.ms/BicepScopes]</p>
</blockquote>
<p>These are the most time-consuming to fix, essentially you need to</p>
<ul>
<li>use the schema reference of the actual resource (so for diagnostic settings us <code>microsoft.insights/diagnosticSettings@2017-05-01-preview</code>) instead of the parent resource <code>/providers</code>/</li>
<li>add a <code>scope</code> referencing the parent resource</li>
<li>rename so as not to reference the parent resource</li>
<li>remove unnecessary properties and <code>dependsOn</code></li>
</ul>
<p><strong>Before</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">functionAppLogAnalytics</span><span class="w"> </span><span class="s">&#39;Microsoft.Web/sites/providers/diagnosticSettings@2017-05-01-preview&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">functionAppName</span><span class="si">}</span><span class="s">/Microsoft.Insights/LogAnalytics&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">tags</span><span class="p">:</span><span class="w"> </span><span class="nv">tagsVar</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;LogAnalytics&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">workspaceId</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceId</span><span class="p">(</span><span class="nv">logAnalyticsResourceGroup</span><span class="p">,</span><span class="w"> </span><span class="s">&#39;Microsoft.OperationalInsights/workspaces&#39;</span><span class="p">,</span><span class="w"> </span><span class="nv">logAnalyticsWsName</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">logs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">category</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;FunctionAppLogs&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">dependsOn</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">functionApp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>After</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">functionAppLogAnalytics</span><span class="w"> </span><span class="s">&#39;microsoft.insights/diagnosticSettings@2017-05-01-preview&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="nv">LogAnalytics</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">scope</span><span class="p">:</span><span class="w"> </span><span class="nv">functionApp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">workspaceId</span><span class="p">:</span><span class="w"> </span><span class="nv">logAnalyticsResourceId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">logs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">category</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;FunctionAppLogs&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="should-you-migrate">Should you migrate?</h2>
<p>A resounding yes from me! The process above goes quite quickly, and I was on shiny new Bicep templates in less than an hour. Bicep is a much friendlier syntax to work with and the IDE support is great. What I also enjoyed is cleaning up all the errors that were present in my ARM templates that would have remained if not for the migration.</p>
<p>There is a neat comparison of ARM syntax vs Bicep syntax here which highlights a lot of the constructs that become simpler as well: <a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/compare-template-syntax" target="_blank" rel="noopener noreffer">https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/compare-template-syntax</a>
</p>
<p>Enjoy the migration! I will be playing around with modules and reuse next and will share what I find.</p>
<p>Featured image background by <a href="https://unsplash.com/@jannerboy62?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreffer">Nick Fewings</a>
 on <a href="https://unsplash.com/s/photos/bird-migrating?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreffer">Unsplash</a>
.</p>
]]></description></item><item><title>Azure Pipelines and Dependabot</title><link>https://www.rickroche.com/2021/05/azure-pipelines-and-dependabot/</link><pubDate>Mon, 31 May 2021 11:21:36 +0200</pubDate><author>
Richard Roché</author><guid>https://www.rickroche.com/2021/05/azure-pipelines-and-dependabot/</guid><description><![CDATA[<div class="featured-image">
                <img src="https://www.rickroche.com/2021/05/azure-pipelines-and-dependabot/dependabot-cover.png" referrerpolicy="no-referrer">
            </div><p>Keeping your dependencies up to date in a project is a really easy way to try and keep the software secure. New releases of a dependency often include</p>
<ul>
<li>Patches for security vulnerabilities!</li>
<li>Performance improvements!</li>
<li>Awesome new features!</li>
<li>Bug fixes!</li>
</ul>
<p>It can also be quite a boring activity and can be time-consuming for a team maintaining the project to run updates regularly into production. Fortunately tools like <a href="https://dependabot.com/" target="_blank" rel="noopener noreffer">Dependabot</a>
 exist!</p>
<h2 id="dependabot">Dependabot?</h2>
<p><a href="https://dependabot.com/" target="_blank" rel="noopener noreffer">Dependabot</a>
 creates pull requests on your repos with the dependencies you should update. You can read about <a href="https://dependabot.com/#how-it-works" target="_blank" rel="noopener noreffer">how it works</a>
; but in a nutshell</p>
<ul>
<li>it checks for updates of your dependencies</li>
<li>it opens up a pull request on your repo</li>
<li>you review the PR and merge</li>
</ul>
<p>This is an awesome tool to save you time and I find it makes keeping your dependencies up to date really easy.</p>
<h2 id="integration-with-azure-pipelines">Integration with Azure Pipelines</h2>
<p>Dependabot is baked into the <a href="https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/about-dependabot-version-updates" target="_blank" rel="noopener noreffer">GitHub ecosystem</a>
 and really easy to use there. Recently I needed to solve this problem on a project in <a href="https://azure.microsoft.com/en-us/services/devops/" target="_blank" rel="noopener noreffer">Azure DevOps</a>
 using <a href="https://azure.microsoft.com/en-us/services/devops/pipelines/" target="_blank" rel="noopener noreffer">Azure Pipelines</a>
 and thought I would share my solution.</p>
<p>If you search the <a href="https://marketplace.visualstudio.com/" target="_blank" rel="noopener noreffer">Azure DevOps Extension Marketplace</a>
 for Dependabot you will find <a href="https://marketplace.visualstudio.com/items?itemName=tingle-software.dependabot" target="_blank" rel="noopener noreffer">this extension</a>
 made by <a href="https://tingle.software/" target="_blank" rel="noopener noreffer">Tingle Software</a>
. It is always great to find extensions to speed up integration and I gave this one a whirl.</p>
<p>The extension is feature rich and works really well with little configuration required. Simply add the below to an Azure Pipeline and run it, and you&rsquo;ll end up with a host of PR&rsquo;s!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">CheckDependencies</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Check Dependencies&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">job</span><span class="p">:</span><span class="w"> </span><span class="l">Dependabot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Run Dependabot&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">pool</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">vmImage</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;ubuntu-latest&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">dependabot@1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Run Dependabot&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">packageManager</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;nuget&#34;</span><span class="w"> </span><span class="c"># Examples: nuget, maven, gradle, npm, etc. Add multiple tasks if multiple package managers are used in your solution</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">targetBranch</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;main&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">openPullRequestsLimit</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w"> </span><span class="c"># Limits the number of PR&#39;s you get</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">setAutoComplete</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># Saves us one click, once our PR policies pass, the update will merge</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Have a look at all the <a href="https://github.com/tinglesoftware/dependabot-azure-devops/blob/main/src/extension/README.md#task-parameters" target="_blank" rel="noopener noreffer">Task Parameters</a>
 that the extension supports to fine tune your implementation. The above checks for <code>nuget</code> dependencies on the <code>main</code> branch, limits the number of PR&rsquo;s raise to be 10 and sets the PR to autocomplete (letting our branch policies and PR validation pipelines perform all their checks).</p>
<h3 id="work-item-linking">Work Item Linking</h3>
<p>In the project I&rsquo;m working in we have a policy set on all PR&rsquo;s to ensure they are linked to a work item. When I ran the above pipeline, I ended up with 10 PR&rsquo;s, none of which were linked to a work item so I couldn&rsquo;t quickly review and merge each one. The extension handles this by allowing you to pass in a <code>workItemId</code> parameter.</p>
<div class="details admonition note open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-pencil-alt fa-fw" aria-hidden="true"></i>Changes in release 0.5<i class="details-icon fas fa-angle-right fa-fw" aria-hidden="true"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">In the upcoming 0.5 release of the extension, the <code>workItemId</code> parameter has been <a href="https://github.com/tinglesoftware/dependabot-azure-devops/pull/130" target="_blank" rel="noopener noreffer">renamed</a>
 to <code>milestone</code> so please be on the look out for this change!</div>
        </div>
    </div>
<p>The Microsoft team have an <a href="https://marketplace.visualstudio.com/items?itemName=mspremier.CreateWorkItem" target="_blank" rel="noopener noreffer">extension for creating work items</a>
 which is really configurable. For my teams workflow, we wanted to have a User Story on our board with all the PR&rsquo;s linked to it. We also didn&rsquo;t want the pipeline creating duplicate User Stories every time it runs if we already had one open that we are working on. After a bit of trial and error, adding the below to the pipeline gave us the desired result.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">CreateWorkItem@1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Create User Story for Dependabot&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">workItemType</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;User Story&#34;</span><span class="w"> </span><span class="c"># We wanted a User Story created, but all work item types are available</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Update Dependencies&#34;</span><span class="w"> </span><span class="c"># The title of the work item</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Adding some tags to the work item</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">fieldMappings</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">      Tags=dependabot; dependencies</span><span class="w">      
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">areaPath</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;your\area&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">iterationPath</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;your\iteration&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Adding duplicate detection, using the area path, iteration path and title to match</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">preventDuplicates</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">keyFields</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">      System.AreaPath
</span></span></span><span class="line"><span class="cl"><span class="sd">      System.IterationPath
</span></span></span><span class="line"><span class="cl"><span class="sd">      System.Title</span><span class="w">      
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Create output variables for the next task to be able use the work item ID</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">createOutputs</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">outputVariables</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">      workItemId=ID</span><span class="w">      
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">dependabot@1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Run Dependabot&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">packageManager</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;nuget&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetBranch</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;main&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">openPullRequestsLimit</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">workItemId</span><span class="p">:</span><span class="w"> </span><span class="l">$(workItemId)</span><span class="w"> </span><span class="c"># This uses the output from the above as the work item to link the PR&#39;s to</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">setAutoComplete</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="docker-caching">Docker Caching</h3>
<p>The <a href="https://marketplace.visualstudio.com/items?itemName=tingle-software.dependabot" target="_blank" rel="noopener noreffer">Dependabot extension</a>
 depends on a Docker image hosted on <a href="https://hub.docker.com/r/tingle/dependabot-azure-devops/tags?page=1&amp;ordering=last_updated" target="_blank" rel="noopener noreffer">Docker Hub</a>
. The image is around <code>4.4GB</code> in size and can take some time to download in the pipeline.</p>
<p>In the docs it mentions</p>
<blockquote>
<p>Since this task makes use of a docker image, it may take time to install the docker image. The user can choose to speed this up by using <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/release/caching?view=azure-devops#docker-images" target="_blank" rel="noopener noreffer">Caching for Docker</a>
 in Azure Pipelines.</p>
</blockquote>
<p>The caching tasks can look a bit confusing, breaking it down you need 3 parts:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">Cache@2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">docker | &#34;${{ variables.imageToCache }}&#34;</span><span class="w"> </span><span class="c"># image to look for in the cache, e.g. tingle/dependabot-azure-devops:0.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">$(Pipeline.Workspace)/docker</span><span class="w"> </span><span class="c"># path of the cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cacheHitVar</span><span class="p">:</span><span class="w"> </span><span class="l">DOCKER_CACHE_HIT</span><span class="w"> </span><span class="c"># variable to set to true if a cache hit is found</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Docker images</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>This checks if there is a cached version of the image to be used. If yes, <code>DOCKER_CACHE_HIT</code> is set to <code>true</code>, otherwise <code>false</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">script</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    docker load -i $(Pipeline.Workspace)/docker/cache.tar</span><span class="w">    
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="l">Restore Docker image</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">and(not(canceled()), eq(variables.DOCKER_CACHE_HIT, &#39;true&#39;))</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>If we have a cached version of the image, we need to pipeline to download it. This step only runs if <code>DOCKER_CACHE_HIT</code> is set to <code>true</code> and will download the cached archive and load it into the docker context.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">script</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    mkdir -p $(Pipeline.Workspace)/docker
</span></span></span><span class="line"><span class="cl"><span class="sd">    docker pull -q ${{ parameters.imageToCache }}
</span></span></span><span class="line"><span class="cl"><span class="sd">    docker save -o $(Pipeline.Workspace)/docker/cache.tar ${{ parameters.imageToCache }}</span><span class="w">    
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="l">Save Docker image</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">and(not(canceled()), or(failed(), ne(variables.DOCKER_CACHE_HIT, &#39;true&#39;)))</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>If we do not have a cached version of the image we need to pull it down from Docker Hub and save it as an archive to be cached.</p>
<h2 id="finishing-up">Finishing up</h2>
<p>We have a pipeline that runs on a schedule, running all the above steps together. A complete example can be found on GitHub <a href="https://gist.github.com/rick-roche/c083ba72e9acd7635cff3c2757ee04bf" target="_blank" rel="noopener noreffer">here</a>
.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">schedules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0 4 * * 1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Weekly Run&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">always</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">include</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">trigger</span><span class="p">:</span><span class="w"> </span><span class="l">none</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">imageToCache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">tingle/dependabot-azure-devops:0.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">CheckDependencies</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Check Dependencies&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">job</span><span class="p">:</span><span class="w"> </span><span class="l">Dependabot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Run Dependabot&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">pool</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">vmImage</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;ubuntu-latest&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">CreateWorkItem@1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Create User Story for Dependabot&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">workItemType</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;User Story&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Update Dependencies&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">fieldMappings</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                Tags=dependabot; dependencies</span><span class="w">                
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">areaPath</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;your\area&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">iterationPath</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;your\iteration&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">preventDuplicates</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">keyFields</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                System.AreaPath
</span></span></span><span class="line"><span class="cl"><span class="sd">                System.IterationPath
</span></span></span><span class="line"><span class="cl"><span class="sd">                System.Title</span><span class="w">                
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">createOutputs</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">outputVariables</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                workItemId=ID</span><span class="w">                
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">Cache@2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">docker | &#34;${{ variables.imageToCache }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">$(Pipeline.Workspace)/docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">cacheHitVar</span><span class="p">:</span><span class="w"> </span><span class="l">DOCKER_CACHE_HIT</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Docker images</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">script</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">              docker load -i $(Pipeline.Workspace)/docker/cache.tar</span><span class="w">              
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="l">Restore Docker image</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">and(not(canceled()), eq(variables.DOCKER_CACHE_HIT, &#39;true&#39;))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">script</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">              mkdir -p $(Pipeline.Workspace)/docker
</span></span></span><span class="line"><span class="cl"><span class="sd">              docker pull -q ${{ variables.imageToCache }}
</span></span></span><span class="line"><span class="cl"><span class="sd">              docker save -o $(Pipeline.Workspace)/docker/cache.tar ${{ variables.imageToCache }}</span><span class="w">              
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="l">Save Docker image</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">and(not(canceled()), or(failed(), ne(variables.DOCKER_CACHE_HIT, &#39;true&#39;)))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">dependabot@1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">displayName</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Run Dependabot&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">packageManager</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;nuget&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">targetBranch</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;main&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">openPullRequestsLimit</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">workItemId</span><span class="p">:</span><span class="w"> </span><span class="l">$(workItemId)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">setAutoComplete</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>In summary the pipeline does the following for you</p>
<ul>
<li><i class="far fa-check-square fa-fw" aria-hidden="true"></i> Runs on a weekly schedule (update the cron to suite your needs)</li>
<li><i class="far fa-check-square fa-fw" aria-hidden="true"></i> Creates a new work item, with the tags we would like (avoiding duplicates)</li>
<li><i class="far fa-check-square fa-fw" aria-hidden="true"></i> Tries to use a cached version of the image for faster builds (creating a cached version if not found)</li>
<li><i class="far fa-check-square fa-fw" aria-hidden="true"></i> Run Dependabot, limiting the number of open PR&rsquo;s to 10, linking the PR&rsquo;s to the created work item and completing the PR once all the policies pass</li>
</ul>
<p>Hopefully this will help you to get your projects running in Azure DevOps nice and up to date!</p>]]></description></item><item><title>Creating This Website</title><link>https://www.rickroche.com/2021/01/creating-this-website/</link><pubDate>Mon, 04 Jan 2021 14:05:53 +0200</pubDate><author>
Richard Roché</author><guid>https://www.rickroche.com/2021/01/creating-this-website/</guid><description><![CDATA[<div class="featured-image">
                <img src="https://www.rickroche.com/2021/01/creating-this-website/install-website-cover.png" referrerpolicy="no-referrer">
            </div><p>I wanted to create a super simple website that is easy to maintain and easy to add content to. You are reading this on the finished product, here is what makes it tick.</p>
<p>My searches started looking around for static website generators (I was hoping that I could run this without a database and all that jazz that normally gets dragged along) and stumbled upon <a href="https://gohugo.io/" target="_blank" rel="noopener noreffer">Hugo</a>
 whose <a href="https://github.com/gohugoio/hugo" target="_blank" rel="noopener noreffer">GitHub README</a>
 describes it as</p>
<blockquote>
<p>A Fast and Flexible Static Site Generator built with love by <a href="https://github.com/bep" target="_blank" rel="noopener noreffer">bep</a>
, <a href="http://spf13.com/" target="_blank" rel="noopener noreffer">spf13</a>
 and <a href="https://github.com/gohugoio/hugo/graphs/contributors" target="_blank" rel="noopener noreffer">friends</a>
 in <a href="https://golang.org/" target="_blank" rel="noopener noreffer">Go</a>
.</p>
</blockquote>
<p>This immediately ticked a few boxes for me being a Go fan, being able to keep everything in version control and write my posts in markdown. I dived right in by following the <a href="https://gohugo.io/getting-started/quick-start/" target="_blank" rel="noopener noreffer">Quick Start</a>
 and was up and running in a couple of minutes. Very awesome developer experience.</p>
<p>After reading <a href="https://flaviocopes.com/start-blog-with-hugo/" target="_blank" rel="noopener noreffer">How to start a blog using Hugo</a>
 I fiddled around with a few of the <a href="https://themes.gohugo.io/" target="_blank" rel="noopener noreffer">themes</a>
 available, initially choosing <a href="https://themes.gohugo.io/anatole/" target="_blank" rel="noopener noreffer">anatole</a>
 to get something up and running.</p>
<h2 id="the-basics">The basics</h2>
<p>With my initial investigations running on my local machine, the time for continuous deployment had arrived! First step: Get the code into version control&hellip; For me this means creating repo on <a href="https://github.com/" target="_blank" rel="noopener noreffer">GitHub</a>
 and pushing my initial code to <a href="https://stevenmortimer.com/5-steps-to-change-github-default-branch-from-master-to-main/" target="_blank" rel="noopener noreffer"><code>main</code></a>
.</p>
<p>For the deployment of the website I decided to use <a href="https://www.netlify.com/" target="_blank" rel="noopener noreffer">Netlify</a>
. It&rsquo;s awesome and has a great developer experience - essentially add the site, linking your repo on GitHub (or other supported version control), Hugo gets picked up automagically and after hitting Deploy Site! you will be up and running in less than a minute. There is a <a href="https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/" target="_blank" rel="noopener noreffer">Step-by-Step Guide</a>
 to get you on your way if you get stuck.</p>
<p>I gave my site a nice name (<code>rick-roche</code>) to test using <a href="https://rick-roche.netlify.app" target="_blank" rel="noopener noreffer">rick-roche.netlify.app</a>
 immediately and pointed my domain to the Netlify name servers (DNS propagation time can take up to 72 hours) for this site to be live.</p>
<h2 id="finishing-up">Finishing up</h2>
<p>There were a few features I was looking for such as search, extended markdown for adding copyable code snippets and the ability to add tips and notes like I am used to doing when using Confluence for work. I found the <a href="https://themes.gohugo.io/loveit/" target="_blank" rel="noopener noreffer">LoveIt</a>
 theme while browsing through the <a href="https://themes.gohugo.io/" target="_blank" rel="noopener noreffer">Hugo Themes</a>
 which had everything I was looking for.</p>
<p>A few updates later (LoveIt has a lot of configurable options), code pushed to GitHub and an automatic deployment thanks to Netlify and you are looking at the results.</p>
<p>Allowing Hugo and Netlify to do the heavy lifting I have ended up with a website that</p>
<ul>
<li>uses an open-source web framework (I like to be able to see the code)</li>
<li>allows me to easily write content using markdown</li>
<li>has continuous deployment for rapid publishing</li>
<li>my entire website can live in version control</li>
</ul>
<h2 id="references">References</h2>
<p>I googled and read a bunch as I hacked my way through getting this site up. Here are links to the content that stood out!</p>
<ul>
<li><a href="https://gohugo.io/" target="_blank" rel="noopener noreffer">Hugo</a>
</li>
<li><a href="https://themes.gohugo.io/" target="_blank" rel="noopener noreffer">Hugo Themes</a>
 (I used the awesome <a href="https://themes.gohugo.io/loveit/" target="_blank" rel="noopener noreffer">LoveIt</a>
 theme</li>
<li><a href="https://flaviocopes.com/start-blog-with-hugo/" target="_blank" rel="noopener noreffer">Start a blog with Hugo</a>
</li>
<li><a href="https://github.com/spech66/hugo-best-practices" target="_blank" rel="noopener noreffer">Hugo best practices</a>
</li>
<li><a href="https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/" target="_blank" rel="noopener noreffer">A Step-by-Step Guide: Deploying on Netlify</a>
</li>
</ul>
<p>Featured image background by <a href="https://unsplash.com/@nihongraphy?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreffer">NihoNorway graphy</a>
 on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText%22" target="_blank" rel="noopener noreffer">Unsplash</a>
.</p>]]></description></item></channel></rss>